CF1709总结和题解

CF1709 总结和题解

A

B

C

题意

有个合法括号序列,部分字符被 ? 替换了,问是否存在唯一的一种? 的方案,使得括号序列合法,即判断填 ? 使得括号序列合法的方案数是否等于1。存在唯一方案输出 YES,方案不唯一输出 NO

序列长度 \(\sum n\le 2\times 10^5\),测试点数 \(T\leq 5\times 10^4\)

第一行输出测试点总数 \(T\)

之后每一行一个字符串 \(s\) 表示替换掉部分字符后的合法括号序列

题解

考虑一个括号序列,令 ( 为 1,) 为 -1,记形成的新序列为 \(c\),然后对该序列做前缀和得到 \(s\),容易发现括号序列合法的充要条件是 \(s_n=0\)\(\forall i\in[1,n],s_i\ge0\)

已知给定的带 ? 的序列是一个合法序列变来的,所以考虑统计三种字符的个数,?,(,) 分别个数记为 cnt cnt0 cnt1,发现如果出现 cnt + min(cnt0,cnt1)=n/2 一定是 YES,因为每个 ? 的填法已经确定了。

因为一定要合法,所以 \(s_n\) 一定为 0,现在就需要让每一个 \(s_i\) 尽量大,方法就是把 ( 尽量往前面填,这样会让每个 \(s_i\) 都尽可能大。

然后我们考虑一种稍稍不那么优秀的方案,就是交换一组由两个 ? 构成的 () 序列,发现交换 () 交界处的那两个一定最优,所以求一下交界处的 min 看看是否大于 2 就行。

可以证明,如果这样交换不行,那么其它任何交换方案都不行。

我写麻烦了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include<bits/stdc++.h>
#define y1 y3647
#define INF 1000000000
#define LL long long
#define pii pair<int,int>
using namespace std;
template<typename _type>
inline void read(_type &x){
x=0;int f=1;char ch=getchar();
while(ch!=45&&(ch>'9'||ch<'0'))ch=getchar();
if(ch==45){f=-1,ch=getchar();}
while(ch<='9'&&ch>='0'){x=x*10+ch-48;ch=getchar();}x*=f;
}
template<typename _type1,typename _type2>void cmin(_type1 &a,const _type2 b){if(a>b)a=b;}
template<typename _type1,typename _type2>void cmax(_type1 &a,const _type2 b){if(a<b)a=b;}
const int N=1e6+10;
int i,j,k,n,s,t,m,tp1,tp2;
char ch[N];
int c[N];
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// freopen(".in","w",stdout);
read(t);
while(t--){
scanf("%s",ch+1);
n=strlen(ch+1);int cnt=0,cnt0=0,cnt1=0;
for(i=1;i<=n;i++){
if(ch[i]=='?')cnt++;
if(ch[i]=='(')cnt0++;
if(ch[i]==')')cnt1++;
}
if(cnt<=1||cnt+min(cnt0,cnt1)==n/2){
puts("YES");
continue;
}
int flag=0,top=0,t1=0;
for(i=1;i<=n;i++){
if(ch[i]=='(')top++;
if(ch[i]==')')top--;
if(ch[i]=='?'&&cnt0!=n/2)top++,t1++,cnt0++;
else if(ch[i]=='?'&&cnt0==n/2)top--;
c[i]=top;
}
int lst=0,min_val=INF,all=0;
for(i=1;i<=n;i++){
if(ch[i]=='?'){
all++;
if(min_val>=2&&all==t1+1)flag=1;
lst=i;
min_val=INF;
}
cmin(min_val,c[i]);
}
if(flag)puts("NO");
else puts("YES");
}
return 0;
}


D

题意

有一个 \(n\times m\) 的网格,第 \(i(i\in[1,m])\) 列的 \([1,a_i]\) 行被锁定了。

你有个机器人,你可以给它发命令,让它向上下左右移动一格,但是机器人有 Bug,你发的每个命令都会被执行 \(k\)不是瞬移 \(k\) 格,而是走 \(k\) 次,每次一格。在任何一个时刻,机器人都不能处于被锁定的格子或者网格外。

给定 \(q\) 组询问,每组询问给定五个参数 \(x_s,y_s,x_f,y_f,k\),代表起点终点坐标和参数 \(k\),问能否从起点到终点,能输出 YES,不能输出 NO

第一行输入 \(n,m\)

第二行输入一个长度为 \(m\) 的数组表示 \(a\)

第三行一个整数 \(q\)

接下来 \(q\) 行每行 \(5\) 个整数 \(x_s,y_s,x_f,y_f,k\) 描述一个询问。

$ 1 n ^9 ; 1 m ^5 ; 1q ^5$

\(a[y_s] < x_s \le n ; 1 \le y_s \le m ; a[y_f] < x_f \le n; 1 \le y_f \le m; 1 \le k \le 10^9\)

题解

假设全部点都没有 \(lock\),考虑从 \((x_1,y_1)\) 能到 \((x_2,y_2)\) 的充要条件,显然是 \(k|abs(x_1-x_2)\) 并且 \(k|abs(y_1-y_2)\)

现在加入限制。允许 \([n-a_i,n]\) 走太麻烦了,翻转一下,变成允许 \([1,a_i']\) 走。

然后最优方案就是先走到当前的最底下,然后横着走过去。

所以找到最低能走到哪个位置,然后做个区间 min,判断最低位置 \(x\) 和区间 min 的关系,如果 \(x\le \min\),显然可以,否则不行。

区间 min 用 ST 表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include<bits/stdc++.h>
#define y1 y3647
#define INF 1000000000
#define LL long long
#define pii pair<int,int>
using namespace std;
template<typename _type>
inline void read(_type &x){
x=0;int f=1;char ch=getchar();
while(ch!=45&&(ch>'9'||ch<'0'))ch=getchar();
if(ch==45){f=-1,ch=getchar();}
while(ch<='9'&&ch>='0'){x=x*10+ch-48;ch=getchar();}x*=f;
}
template<typename _type1,typename _type2>void cmin(_type1 &a,const _type2 b){if(a>b)a=b;}
template<typename _type1,typename _type2>void cmax(_type1 &a,const _type2 b){if(a<b)a=b;}
const int N=4e5+10;
int i,j,k,n,s,t,m,q;
int st[25][N],lo[N];
int ask(int l,int r){
if(l>r)swap(l,r);
int len=lo[r-l+1];
return min(st[len][l],st[len][r-(1<<len)+1]);
}
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// freopen(".in","w",stdout);
read(m),read(n);

for(i=2;i<=n;i++)lo[i]=lo[i>>1]+1;
for(i=1;i<=n;i++)
read(st[0][i]),st[0][i]=m-st[0][i];

for(i=1;i<=20;i++)
for(j=1;j+(1<<i)-1<=n;j++)
st[i][j]=min(st[i-1][j],st[i-1][j+(1<<i-1)]);
read(q);
for(i=1;i<=q;i++){
int x1,x2,y1,y2;
read(y1),read(x1),read(y2),read(x2),read(k);
if(k==0){
if(x1==x2&&y1==y2)puts("YES");
else puts("NO");
continue;
}
y1=m-y1+1,y2=m-y2+1;
if(abs(x1-x2)%k||abs(y1-y2)%k||ask(x1,x2)<(y1%k==0?k:y1%k))puts("NO");
else puts("YES");
}
return 0;
}


E

题意

你有一棵无根树,点数为 \(n\),每个点有个点权 \(a_u\),定义一条路径 \(P(u,v)\) 的权值为经过的所有点的点权的异或和。定义一棵树是合法的,当且仅当树上所有简单路径(只经过每个点一次的路径)的的权值都不为 \(0\)

你可以对权值进行修改,可以改成任意正整数,问最少修改多少次才能让这棵树合法。

输出最小修改次数。

\(n\leq 2\times 10^5,a_i\leq 2^{30}\)

题解

发现路径权值为 \(sum[u] \bigoplus sum[v] \bigoplus a[lca(u,v)]\)\(sum[u]\) 表示从 \(1\)\(u\) 的路径上所有点点权的异或和,每一条路径,我们在路径 \(lca\) 处考虑是否不合法(权值为 0)。

我们称一个点不合法当且仅当有一条 \(lca\) 为这个点的不合法路径经过它。我们从深到浅处理每一个不合法的 \(lca\)

如果一条路径不合法,修改方案可以分为改 \(lca\) 和非 \(lca\),可以说明改 \(lca\) 是最优的。首先,可以说明,改一个点 \(u\) 的最优方案之一是改成 \(2^{u+32}\),因为这样的话所有经过这个点的路径都一定合法。然后说明改 \(lca\) 最优。如果不改 \(lca\),以这颗子树中的点为起点的路径仍然有可能不合法,而改 \(lca\),以这颗子树中的点为起点的路径一定都合法。我们假设了当前处理的 \(lca\) 是最深的,所以这颗子树内所有的不合法路径一定经过了 \(lca\),所以改了 \(lca\),所有路径就一定合法了。所以\(lca\) 最优

怎么判断一个 \(lca\) 处有不合法路径,拿个 set 维护下每颗子树内的 \(sum\),然后加入一颗子树时,枚举加入子树的所有点 \(v\),判断当前 set 中是否存在元素 \(a_[lca]\bigoplus sum[v]\),如果存在,\(lca\) 就不合法,我们给答案加一,在这颗子树的父亲计算时忽略该子树。

发现合并 set 的代价是 \(O(\sum size_u)=O(n^2)\) 的,但是可以启发式合并。

具体的,一个点 \(u\) 继承它最大的儿子的 \(set\),然后在把它自己和其它子树加入到这个 set 中,同时判断是否存在不合法路径。考虑这样的复杂度,证明和树链剖分的复杂度证明相同,继承操作可以用 std::swap,这个函数交换两个 STL 容器的复杂度,在 C++11 以上标准是 \(O(1)\) 的。

对于一个点,我们考虑它什么时候会被合并一次(就是作为非最大子树,被合并上去)。每一个点所在的 set,被合并时,它所在的新 set 的大小至少变为 \(2\) 倍(因为新 set 最开始的大小一定比该点所在的 set 大,否则该点所在的 set 会作为最后保留的 set 而不对复杂度做贡献),最后的 set 大小为 \(n\),所以每个点合并 \(\log n\) 次。

总复杂度 \(O(n\log^2 n)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include<bits/stdc++.h>
#define y1 y3647
#define INF 1000000000
#define LL long long
#define pii pair<int,int>
using namespace std;
template<typename _type>
inline void read(_type &x){
x=0;int f=1;char ch=getchar();
while(ch!=45&&(ch>'9'||ch<'0'))ch=getchar();
if(ch==45){f=-1,ch=getchar();}
while(ch<='9'&&ch>='0'){x=x*10+ch-48;ch=getchar();}x*=f;
}
template<typename _type1,typename _type2>void cmin(_type1 &a,const _type2 b){if(a>b)a=b;}
template<typename _type1,typename _type2>void cmax(_type1 &a,const _type2 b){if(a<b)a=b;}
const int N=2e5+10;
int i,j,k,n,s,t,m,tp1,tp2,ans;
int fa[N],sz[N],son[N],a[N],sum[N];
vector<int> e[N];
set<int> st[N];
void pre_dfs(int u){
sz[u]=1;
for(auto v:e[u]){
if(fa[u]==v)continue;
fa[v]=u;sum[v]=sum[u]^a[v];
pre_dfs(v),sz[u]+=sz[v];
if(sz[v]>sz[son[u]])son[u]=v;
}
}
void redfs(int u,int flag=0){
if(son[u]){
redfs(son[u]);
swap(st[son[u]],st[u]);
}
if(st[u].find(sum[u]^a[u])!=st[u].end())flag=1;
st[u].insert(sum[u]);
for(auto v:e[u]){
if(v==son[u]||v==fa[u])continue;
redfs(v);
for(auto val:st[v]){
if(st[u].find(val^a[u])!=st[u].end())
flag=1;
}
for(auto val:st[v])
st[u].insert(val);
}
if(flag)ans++,st[u].clear();
}
signed main()
{
read(n);
for(i=1;i<=n;i++)read(a[i]);
sum[1]=a[1];
for(i=1;i<n;i++){
int x,y;read(x),read(y);
e[x].push_back(y),e[y].push_back(x);
}
pre_dfs(1);
redfs(1);

cout<<ans<<endl;
return 0;
}


F

题意

定义二进制串为只包含 \(01\) 的字符串。

给你 \(n,k,f\)\(1\le n\leq 15,1\le f,k\leq 2\times 10^5\),你需要给每个长度不超过 \(n\) 的二进制串 \(s\) 确定一个 \(c_s\in [0,k]\) 的权值。然后你需要选出一个只包含长度恰好\(n\) 的二进制串的可重集合,使得这个可重集合最大。并且满足对于所有长度小于 \(n\) 的二进制串 \(s\),集合元素中,\(s\) 作为集合中元素前缀的次数不超过 \(c_s\)

求安排 \(c\) ,使得集合最大大小恰好为 \(f\) 的方案数,对 \(998244353\) 取模。

题解

考虑取到最大值 \(f\) 的条件,不妨令 \(n=1\),发现要求就是 \(c_0+c_1=f\),然后我们考虑一些长度为 \(n\) 的二进制串。

因为要对每个长度小于 \(n\) 的串都确定一个 \(c\),然后我们又发现,记一个前缀 \(s\) 的数量限制为 \(w_s\),实际上 \(w_s=\min(c_s,w_{s+'0'}+w_{s+'1'})\)

然后我们就可以对一个确定的 \(c\) 序列,像树形 \(dp\) 一样算出最大大小。

考虑对这个树形 \(dp\) 的过程计数,本质上,由于树是满二叉树,所以 dp 出每个值的 \(c\) 序列的方案只于层数有关。

所以设 \(dp[i][j]\) 为考虑到深度为 \(n-i\) 的满二叉树,值为 \(j\) 的安排方案总数。

转移限制有两种,第一个是 \(c\) 限制,第二种是子树限制,考虑先算一个 \(sum[i][j]\) 表示子树和为 \(j\) 的方案数。

然后可以得到 \(dp[i][j]=sum[i][j]*(k-j+1)+\sum\limits_{t> j}sum[i][t]\)。分别表示两种限制的贡献。

算出 \(sum\) 后是 \(O(k)\) 转移的。考虑 \(sum[i][j]=\sum\limits_{s+t=j} dp[i-1][s]\times dp[i-1][j]\)。就是个卷积,NTT 板子上去就行。

注意边界,处理最顶层的时候,\(dp[n][i]\) 只能由子树和来限制,因为这个点本来就是长度为 0 的串,不能填 \(c\),我在这里想当然的认为只要 \(i\le k\) 的时候有值,所以没有 AK,实际上这里 \(k< i\le 2k\) 也是有值的,因为这个点本身不会限制。

如果 TLE 了换成 64bit 的机子再交一次,64 bit 的 long long 运算要快一些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include<bits/stdc++.h>
#define y1 y3647
#define INF 1000000000
#define int long long
#define pii pair<int,int>
using namespace std;
template<typename _type>
inline void read(_type &x){
x=0;int f=1;char ch=getchar();
while(ch!=45&&(ch>'9'||ch<'0'))ch=getchar();
if(ch==45){f=-1,ch=getchar();}
while(ch<='9'&&ch>='0'){x=x*10+ch-48;ch=getchar();}x*=f;
}
template<typename _type1,typename _type2>void cmin(_type1 &a,const _type2 b){if(a>b)a=b;}
template<typename _type1,typename _type2>void cmax(_type1 &a,const _type2 b){if(a<b)a=b;}
const int N=1<<19,mod=998244353;
namespace NTT{
const int N=1<<20,mod=998244353,g=3,gi=332748118;
int i,j,k,n,s,t,m;
int a[N],b[N],rk[N];
int quick(int a,int s,int ret=1){
while(s){
if(s&1)ret=ret*a%mod;
a=a*a%mod,s>>=1;
}
return ret;
}
void NTT(int *a,int type)
{
for(i=0;i<1<<s;i++)
if(rk[i]>i)swap(a[rk[i]],a[i]);
for(int len=1;len<=s;len++)
{
int w=1,wn=quick(g,mod-1>>len);
if(type==-1)wn=quick(wn,mod-2);
for(j=0;j+(1<<len)<=1<<s;j+=1<<len,w=1)
{
for(k=j;k<j+(1<<len-1);k++,w=1ll*w*wn%mod)
{
int x=a[k],y=a[k+(1<<len-1)];
a[k]=(x+1ll*w*y%mod)%mod,a[k+(1<<len-1)]=(x-1ll*w*y%mod)%mod;
}
}
}
}
void init(int nn,int c[],int res[]){
n=m=nn;
memset(a,0,sizeof(a)),memset(b,0,sizeof(b));
for(i=0;i<=n;i++)a[i]=c[i];
for(i=0;i<=m;i++)b[i]=c[i];
while(1<<s<=n+m)s++;

for(i=1;i<1<<s;i++)rk[i]=rk[i>>1]>>1|(i%2?1<<s-1:0);
NTT(a,1),NTT(b,1);
for(i=0;i<1<<s;i++)a[i]=1ll*a[i]*b[i]%mod;
NTT(a,-1);int inv=quick(1<<s,mod-2);
for(i=0;i<=n+m;i++)
res[i]=(1ll*a[i]*inv%mod+mod)%mod;
}
}
int i,j,k,n,s,t,m,tp1,tp2,f;
void Inc(int &a,int b){
a+=b;if(a>=mod)a-=mod;
}
int quick(int a,int s,int ret=1){
while(s){
if(s&1)ret=ret*a%mod;
a=a*a%mod,s>>=1;
}
return ret;
}

int dp[16][N<<1],sum[16][N<<1],p[N];
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
// freopen(".in","w",stdout);
read(n),read(k),read(f);
for(i=0;i<=k;i++)dp[0][i]=1;
for(i=1;i<=n;i++){
NTT::init(k,dp[i-1],sum[i]);
for(j=0;j<=2*k;j++){
if(i!=n)dp[i][j]=sum[i][j]*(k-j+1)%mod;
else dp[i][j]=sum[i][j];
}
if(i!=n)for(j=2*k-1;j>=0&&i!=n;j--){
if(j<=k)Inc(dp[i][j],sum[i][j+1]);
Inc(sum[i][j],sum[i][j+1]);
}
}
cout<<(dp[n][f]+mod)%mod;
return 0;
}