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 () { 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 \le n \le 10^9 ; 1 \le m \le 2 \cdot 10^5 ; 1\le q \le 2\times 10^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 () { 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 () { 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 ; }