AtCoder,Codeforces做题记录
AGC024(5.20)
总结:猜结论,“可行即最优”
B: 给定一个n的排列,每次可以将一个数移到开头或结尾,求变成1,2,...,n所需的最小步数。
找到一个最长的i,i+1,...,j满足在排列中的位置递增,这些数可以保留,答案就是n-(j-i+1)。
C: 给定一个数列A,初始数列X全为0,每次可以让X[i+1]=X[i]+1,求最少多少次能变成A。
首先特判掉A[1]>0和A[i+1]>A[i]+1的情况,然后如果A[i+1]=A[i]+1,那么只需要ans++,否则ans+=A[i+1]。
D: 给你一棵无根树,你可以在树中任意新增点,然后将树染色,满足以同一颜色的任意两个点为根的两棵树同构,问最少要多少种颜色,且在颜色数最少的情况下,这棵树最少有多少个度数为1的点。
找到一个点使以这个点为根的树深度最小(这个深度同时也是$\lceil \frac{D}{2} \rceil$,D为树的直径长)。
如果使得深度最小的点有两个,那么显然要让这棵树关于这两个点轴对称。如果有一个,可以使整棵树关于这个点中心对称,也可以任选一个和这个点相邻的点,让这棵树关于这两个点轴对称。两种方案讨论一下即可。
E: 感觉非常巧妙,将模型转化到树上再做DP
https://blog.csdn.net/qq_33229466/article/details/80390249
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=l; i<=r; i++) 5 typedef long long ll; 6 using namespace std; 7 8 const int N=210; 9 int n,u,v,mn,p,cnt,tot,h[N],a[N],d[N],to[N],nxt[N],b[N],dep[N][N]; 10 11 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } 12 13 void dfs(int x,int fa,int k){ 14 dep[k][x]=dep[k][fa]+1; 15 for (int i=h[x],v; i; i=nxt[i]) 16 if ((v=to[i])!=fa) dfs(v,x,k); 17 } 18 19 ll work(int x,int y){ 20 ll res=1; 21 memset(b,0,sizeof(b)); 22 rep(i,1,n){ 23 int p=min(dep[x][i],dep[y][i]); 24 b[p]=max(b[p],d[i]-1); 25 } 26 rep(i,1,n) if (b[i]) res*=b[i]; else break; 27 return res<<1; 28 } 29 30 ll work1(int x){ 31 ll res=1; 32 memset(b,0,sizeof(b)); 33 rep(i,1,n) b[dep[x][i]]=max(b[dep[x][i]],d[i]-1); 34 rep(i,2,n) if (b[i]) res*=b[i]; else break; 35 return res*d[x]; 36 } 37 38 int main(){ 39 freopen("d.in","r",stdin); 40 freopen("d.out","w",stdout); 41 scanf("%d",&n); mn=n+1; 42 rep(i,2,n) scanf("%d%d",&u,&v),add(u,v),add(v,u),d[u]++,d[v]++; 43 rep(i,1,n) dfs(i,0,i); 44 rep(i,1,n) rep(j,1,n) dep[i][0]=max(dep[i][0],dep[i][j]); 45 rep(i,1,n) mn=min(mn,dep[i][0]); 46 rep(i,1,n) if (dep[i][0]==mn) a[++tot]=i; 47 if (tot==2) printf("%d %lld\n",mn-1,work(a[1],a[2])); 48 else{ 49 ll ans=work1(a[1]); 50 for (int i=h[a[1]]; i; i=nxt[i]) a[++tot]=to[i]; 51 rep(i,2,tot) ans=min(ans,work(a[1],a[i])); 52 printf("%d %lld\n",mn,ans); 53 } 54 return 0; 55 }
ABC098(5.26)
D: 给出一个序列,求有多少个区间满足异或和等于加和。
列出xor和+的真值表发现,只要两者相同当且仅当没有一位上两者均为1。
所以当l增加时r也必定单调(tow pointers),用一个bool数组记录每位是否被占用即可。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=l; i<=r; i++) 5 typedef long long ll; 6 using namespace std; 7 8 const int N=400010; 9 int n,a[N]; 10 bool b[30],c[30]; 11 ll ans; 12 13 bool jud(int x){ 14 rep(i,0,20) c[i]=b[i]; 15 rep(i,0,20) if (x&(1<<i)){ 16 if (c[i]) return 0; 17 c[i]=1; 18 } 19 rep(i,0,20) b[i]=c[i]; 20 return 1; 21 } 22 23 int main(){ 24 freopen("d.in","r",stdin); 25 freopen("d.out","w",stdout); 26 scanf("%d",&n); 27 rep(i,1,n) scanf("%d",&a[i]); 28 int l=1,r=1; 29 rep(i,0,20) if (a[1]&(1<<i)) b[i]=1; 30 for (; l<=n; l++){ 31 while (r<n && jud(a[r+1])) r++; 32 ans+=r-l+1; 33 rep(i,0,20) if (a[l]&(1<<i)) b[i]=0; 34 } 35 printf("%lld\n",ans); 36 return 0; 37 }
ECR45(6.10)
D:构造一张图,以邻接矩阵形式输出,如果无解输出NO。满足有n个点,a个连通块,补图有b个连通块。
首先有一个性质,如果一张图不连通,则补图必定连通。
于是可以得出,a和b不能同时大于1.
考虑构造,设b=1,让后a-1个点独立出来(自己形成一个连通块),再让前n-a+1个连通即可。
还有特殊情形,n=2或3时,a和b同时为1是无解的。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 using namespace std; 5 6 const int N=1010; 7 int n,ans[N][N]; 8 9 void pr(){ 10 printf("YES\n"); 11 for(int i=0;i<n;i++){ 12 for(int j=0;j<n;j++) printf("%d",ans[i][j]); 13 printf("\n"); 14 } 15 } 16 17 int main(){ 18 int a,b; scanf("%d%d%d",&n,&a,&b); 19 if(a > 1 && b > 1){ printf("NO\n"); return 0; } 20 if(a == 1 && b == 1){ 21 if(n == 2 || n == 3){ printf("NO\n"); return 0; } 22 for(int i=0;i<n-1;i++) ans[i][i+1] = ans[i+1][i] = 1; 23 pr(); return 0; 24 } 25 if(b == 1){ 26 for(int i=a-1;i<n-1;i++) 27 ans[i][i+1] = ans[i+1][i] = 1; 28 pr(); return 0; 29 } 30 if(a == 1){ 31 for(int i=b-1;i<n-1;i++) ans[i][i+1] = ans[i+1][i] = 1; 32 for(int i=0;i<n;i++) ans[i][i] = 1; 33 for(int i=0;i<n;i++) 34 for(int j=0;j<n;j++) ans[i][j] ^= 1; 35 pr(); 36 } 37 return 0; 38 }
CFR487(div 2) (6.11)
C:构造一张图,让'A','B','C','D'组成的连通块数量恰好为a,b,c,d.(a,b,c,d<=100,构造图的大小不能超过50*50)
有一个巧妙而简单的想法,摘自Command
分成4块然后循环地满足要求,代码也很简单。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=l; i<=r; i++) 4 using namespace std; 5 6 const int N=50; 7 int a,b,c,d; 8 char s[N+5][N+5]; 9 10 void work(int x,int x1,int x2,int y1,int y2,char ch){ 11 for (int i=x1; i<=x2 && x; i+=2) 12 for (int j=y1; j<=y2 && x; j+=2) s[i][j]=ch,x--; 13 } 14 15 int main(){ 16 scanf("%d%d%d%d",&a,&b,&c,&d); a--; b--; c--; d--; puts("50 50"); 17 rep(i,1,25) rep(j,1,50) if (j<=25) s[i][j]='A'; else s[i][j]='B'; 18 rep(i,26,50) rep(j,1,50) if (j<=25) s[i][j]='C'; else s[i][j]='D'; 19 work(d,1,25,1,25,'D'); work(c,1,25,26,50,'C'); work(b,26,50,1,25,'B'); work(a,26,50,26,50,'A'); 20 rep(i,1,50){ rep(j,1,50) putchar(s[i][j]); puts(""); } 21 return 0; 22 }
ARC100(7.1)
D: 给定一个正整数列,要求分割成四段,最小化和最大的一段的和-和最小的一段的和。
当2,3段的分割点确定时,1,2段的分割点一定是让这两段和的差最小的地方,3,4段亦然。
分割点单增,线性处理即可。
CFR493(div 2)(7.1)
C: 给定一个01串,每次可以对任意区间翻转(代价为x)或反转(代价为y),问变成全1串的最小代价。
统计串中连续0的段数,考虑每次翻转可以把两个0合并到一起,每次反转可以把一段0全变成1。
所以如果y<x,最优方案一定是直接反转所有全0段,如果x<y,一定是先把所有全0段合并到一起再做一次反转。
AGC026(7.15)
C: 给定一个2n(n<=18)长的串,将它涂成n个红色和n个蓝色,使红色从左往右读和蓝色从友往左读一样。
显然是暴力枚举的复杂度,但全部暴力显然不行,于是考虑只涂前半部分。设红色涂了a个,则前半部分蓝色就涂了18-a个。蓝色的18-a个确定了后半部分的红色,红色的a个确定了后半部分的蓝色。所以对于后半部分只需要一个DP即可。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=l; i<=r; i++) 5 typedef long long ll; 6 using namespace std; 7 8 const int N=40; 9 int n; 10 ll ans,f[N][N]; 11 bool c[N]; 12 char s[N],a[N],b[N]; 13 14 void work(){ 15 int tot=0,tot2=0; 16 rep(i,1,n) if (c[i]) a[++tot]=s[i]; else b[++tot2]=s[i]; 17 reverse(a+1,a+tot+1); reverse(b+1,b+tot2+1); 18 memset(f,0,sizeof(f)); f[0][0]=1; 19 rep(i,0,n){ 20 rep(j,0,min(i,tot)) if (f[i][j]) { 21 if (j<tot && a[j+1]==s[i+n+1]) f[i+1][j+1]+=f[i][j]; 22 if (i-j<tot2 && b[i-j+1]==s[i+n+1]) f[i+1][j]+=f[i][j]; 23 } 24 } 25 ans+=f[n][tot]; 26 } 27 28 void dfs(int x){ 29 if (x>n){ work(); return; } 30 c[x]=1; dfs(x+1); c[x]=0; dfs(x+1); 31 } 32 33 int main(){ 34 scanf("%d",&n); scanf("%s",s+1); 35 dfs(1); printf("%lld\n",ans); 36 return 0; 37 }
CFR500(div 2)(7.30)
D:一个n*m的01表中,有些为1。当(r1,c1),(r1,c2),(r2,c1)均为1时(r2,c2)可以自动变为1,问最少需要手动将多少个位置变为1才能让整个表均为1。(n,m<=2e5)
考虑对n行建点放左边,m列建点放右边,将所有1的位置(r,c),连左边r和右边c。可以证明自动变1的操作不会改变图的连通性,所以只需要建图后求连通块数量即可。
E:一个数列,定义a[i]为hill仅当a[i-1]<a[i],a[i+1]<a[i]。每次可以花费1的代价让某个位置-1。对所有k,输出让数列至少存在k个hill的最少花费。
f[i][j][0/1]表示前i个位置至少j个hill,且i是/不是hill的最小花费。
f[i][j][0]从j-1转移,f[i][j][1]从j-2转移。
CFR501(div 3)(7.31)
D:构造一个长为k的数列a[1..n],所有元素在[1,n]中,a[0]=1。满足a[i]!=a[i-1],且sum{abs(a[i]-a[i-1])}=s。
每次走min(s-k,n-1)步,可以证明,总有一个方向可以走。
CFR505(div 2)(8.19)
B:给n个数对(a[i],b[i]),输出一个整数k,满足任意i都有k|a[i]或k|b[i]。(n<=1e5,a[],b[]<=1e9)
开始一直想不到正解,写的是:先求c[i]=a[i]*b[i],然后对c[]求gcd得到d,用pollard_rho求出d的最小因子即为答案。
另一种方法:给a[1],b[1]分解质因数,对每个质因数暴力判断即可。
C:一个01序列,每次可以从中切断,两边翻转再接回去,问能构成的最长01间隔串有多长。
注意到操作的本质就是环上换一个起点。倍长求最长间隔即可。注意答案不能超过n。
D:给一张点权图,求一棵生成树,满足树是BST,问是否可行。(n<=700)
常规下会想到dp[rt][l][r]区间DP,但复杂度过高,考虑另一种方式Left[l][r]表示区间[l,r]构成的生成树,且以l为根是否可行,Right[l][r]同理。
转移复杂度降了一维。可能第一种方法用bitset优化也可以通过。
ARC101(8.25)
D:对一个数列的所有区间求一次中位数,再将这n(n-1)/2个中位数放在一起求中位数,输出结果。(n<=1e5)
二分答案ans,问题转化为求有多少个区间中比ans小的数大于这个区间的一半。按照套路将所有<=ans的数赋为0,>ans的数赋为1,则一个区间需要被统计当且进当s[r]-s[l-1]<0,于是转化为经典逆序对问题。
ARC102(9.1)
C:求有多少个有序对(a,b,c)(a,b,c<=n)满足a+b,a+c,b+c均为k的倍数。
k为奇数时当且仅当a,b,c均为k的倍数,k为偶数时当且仅当a,b,c均为k的倍数或模k均为k/2。
E:掷n个无区别的k个面的骰子,对所有i属于[2,2k],输出有多少种掷的方案,满足不存在任何两个骰子向上的面的和为i。
将所有和为i的数两两成对,设有p个对。枚举这p个对中有q个对中有一个数被选上,方案数为$C(p,q)*2^q$。
剩下的就是简单可重组合数问题了。注意特殊处理k/2。
AGC027(9.15)
C:给一个01DFA,判断是否能在DFA上走出任意01串。
当且仅当能走出AABB。不断将图中仅与1中颜色相邻的点或孤立的点删去,如果最后还有剩余点则说明可以。
ECR51(div 2)(9.20)
F:n个点m条边的无向连通边权图,m-n<=20,每次询问任意两点最短路。
显然先建出最小生成树,那么u到v只有两种选择:
1.u->t->v,t是某条非树边上的点。对每个非树边上的点跑一次单源最短路即可。
2.u->lca->v,直接倍增LCA即可。
CFR511(div 2)(9.21)
C:给定n个数,求最少删掉多少数,使得剩下数的gcd大于n个数的gcd。
先全部除以gcd,问题变为找到一个质因子使最多的数包含。(n<=1e5,a<=1e7)
法一:开个桶记录每个数的出现次数,再对每个素数枚举倍数,统计这个素数在多少数中出现。$O(n\ln\ln n)$
法二:线性筛出每个数的最小质因子,分解质因数复杂度优化为$O(\log a)$,于是对每个数质因数分解即可。$O(n\log a)$
CFR512(div 2)(9.23)
D:构造一个矩形{(0,0),(n,m)}内的三角形使面积为nm/k。
由皮克定理,所有三角形一定能变成一个直角三角形,且占用方格数不会变多。
故若约分nm/k后k>2则必定无解。剩下分k=1和k=2讨论即可。
E:n个数,每个数中可以任意交换比特,问有多少区间,使区间内所有数适当操作后异或和可以为0。(n<=1e5)
当且仅当:区间所有数1的个数和为偶数且1最多的那个数的1的个数不超过其它所有数的和。
问题变为:区间和为偶数且Max<=Sum/2。
直接不好求,考虑将所有偶数区间-所有偶数且Max>Sum/2的区间,前者直接前缀和即可。
发现Max最多为log2(1e18),故当Sum大于这个值之后就可以直接中断,故可以nlog(1e18)内求解后者。
AGC028(10.13)
B:一个数列,每次删除一个数,代价是所有与这个数相连的数之和(一个数被删,两边之间就断开了),求所有删数方案的所有删除操作的代价总和。
打表发现完全没有规律,考虑先除以n!转化成期望问题,最后再乘回去。
考虑一个数期望会在多少数被删除时产生贡献。对于位置i,位置j删除时,i与j连通的概率,也就是“i,i+1,...,j这些位置中i是第一个被删的”的概率,显然为1/(|i-j|+1)。
于是可以直接根据概率算出每个数的期望贡献,问题得解。
C:一个图,每个点有两个权值A[],B[],任意u到v均可连边,边权为min(A[u],B[v])。求图的最短Hamilton回路。n<=1e5。
将A[],B[]放在一起共2n个数排序,从前往后选n个,再调整。这样可以保证,如果选出一个可行方案,一定是最优的,就不必考虑min的问题了。
可以证明,当且仅当A和B都在选的n个中至少出现了一次,但不存在i使得A[i]和B[i]都被选上时,方案不合法。这时候只需要把替换成n+1即可。
CFR516(div 2)(10.15)
E:交互,在平面上选n个点,每输出一个点的位置,反馈这个点是黑色还是白色,再由此输出下一个点的位置。要求最终存在直线能将所有黑点划分到一边,白点在另一边,并输出一条这样的直线。n<=30,坐标必须是[0,1e9]中的整数。
实际上题目故意放宽了限制,可以在一条直线上完成这个操作。每次输出的点是当前为确定区间(一开始就是[0,1e9])的中点,根据反馈决定这个点属于左边还是右边,不确定区间减半。
1 int main(){ 2 scanf("%d",&n); n--; l=0,r=1<<n; 3 printf("%d %d\n",0,0); fflush(stdout); c=rd(); 4 for(int m; n; n--){ 5 m=(l+r)>>1,printf("%d %d\n",m,m),fflush(stdout); 6 if (rd()==c) l=m; else r=m; 7 } 8 printf("%d %d %d %d",l,r,r,l); 9 return 0; 10 }
CFR517(div 2)(10.21)
C:在正整数集中选两个不相交的子集,使第一个集合和不超过a,第二个不超过b,求最多能选多少个元素。
先看一个强化版的题:一个序列a[]中满足a[i]<=i,问凑出k的方案。
结论:若a[i]<=i均成立,则a[1]~a[n]能凑出1~sum[n]的所有数。数归证明显然。
于是将所有数从大到小排序后,每次贪心将能选的选入即可。
这题是上面的弱化版,同样贪心选即可。
D:读入一个n*n的字母阵,可以更换最多其中k个,求(1,1)到(n,n)每次走右或下能走出的最小字典序。
先DP统计(1,1)到所有点最少有多少个位置不是'a',将个数不超过k的点全部变成'a',这样就变成裸的从(1,1)到(n,n)的最小字典序了。分层扩展,每次只扩展可能成为答案的位置即可。
ECR55(div 2)(11.29)
D:构造一张无向连通图,每个点有度数上限,最大化图的直径或输出无解。
类似数学自招题的思想,先求出理论最大值,再想办法保证能构造出这个值。
首先当且仅当度数限制之和大于2*(n-1)时无解。
显然最后得到的是一棵树,那么就是一条链(度数限制>=2)旁边连出一些叶子(度数限制=1)。
于是最大值为n-num+1,num为度数限制=1的点的个数。
考虑构造,先处理非叶子,每次找前面最近的一个度数还没达到限制的点连边。
再考虑非叶子,首先若链的首尾可以再连点的化优先往上连,否则随便连。
这样直径就一定是n-num+1了。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 using namespace std; 5 6 const int N=510; 7 int n,k=1,sm,num,lst,a[N]; 8 9 int main(){ 10 freopen("D.in","r",stdin); 11 freopen("D.out","w",stdout); 12 scanf("%d",&n); 13 rep(i,1,n) scanf("%d",&a[i]),sm+=a[i],num+=a[i]==1; 14 if (sm<2*n-2){ puts("NO"); return 0; } 15 printf("YES %d\n%d\n",min(n-1,n-num+1),n-1); 16 rep(i,1,n) if (a[i]>1){ 17 if (lst) printf("%d %d\n",lst,i); else a[i]++; 18 lst=i; 19 } 20 rep(i,1,n) if (a[i]==1){ 21 if (lst){ printf("%d %d\n",i,lst); lst=0; continue; } 22 for (; a[k]<=2; k++); 23 printf("%d %d\n",i,k); a[k]--; 24 } 25 return 0; 26 }
E:一个序列,可以选一段数全部加上某个数,最大化最后数c的个数。(n,a[i]<=5e5)
一个结论:所选区间首尾一定同色,于是每次只需要考虑同色的点,前缀和优化一下即可。
ACC2018(div 2)(12.16)
D:给定一张图和一些关键点,对于每个关键点,输出它到所有其它关键点最短路的最大值。1e5
最小生成树上任意两点间路径一定是这两点间所有路径中最大值最小的那一条。
所以所有关键点的答案都是树上最长的那条边。
E:一个数列,满足所有前缀和都是完全平方数。现给出所有偶数项,输出所有奇数项,要求所有奇数项也都是平方数。
对于每个位置,选择最小的满足条件的平方数一定不劣,且所有奇数项一定单调,于是暴力枚举平方数即可。
CFR523(div 3)(12.19)
D1:一个数列,每次可以进行两种操作中的一种,一是选择两个相邻的相等的数均+1,二是将某个数+2,问最后能否使所有数相等。
显然可以通过操作二使数列中所有数相差只剩一,注意到若有两个相邻的数奇偶性相同则可以将它们删去,因为它不再对答案造成任何影响。
于是维护一个栈,发现栈顶两个数奇偶性相同则弹出这两个数。最后若栈中有超过1个数则无解。1e5
D2:无二操作
同样维护一个栈,若栈顶两数相等(非奇偶性相同)则弹出。若最后剩下超过一个数或剩下的那个数小于所有其他数的最小值则无解。
CFR528(div 2)(12.23)
D:一棵树,所有边权和为s,给每条边分配权值,是树的直径最小。
任意两叶子间距离都一定相等,于是答案为n/叶子数*2。
ECR57(div 2)(12.29)
F:给你一个中间被挖成-1的排列,求这个排列的期望逆序对个数。1e5
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 6 #define per(i,r,l) for (int i=(r); i>=(l); i--) 7 typedef long long ll; 8 using namespace std; 9 10 const int N=400010,mod=998244353,i2=499122177; 11 int n,fz,fm,tot,tmp,ans,s1,d1,s2,d2,a[N],b[N],c[N],w1[N],w2[N],fac[N]; 12 13 int ksm(int a,int b){ 14 int res=1; 15 for (; b; a=1ll*a*a%mod,b>>=1) 16 if (b & 1) res=1ll*res*a%mod; 17 return res; 18 } 19 20 void add(int x,int k){ for (; x<=n; x+=x&-x) c[x]+=k; } 21 int que(int x){ int res=0; for (; x; x-=x&-x) res+=c[x]; return res; } 22 23 int main(){ 24 scanf("%d",&n); 25 rep(i,1,n){ 26 scanf("%d",&a[i]); 27 if (a[i]==-1) tot++; else b[a[i]]=1; 28 } 29 rep(i,1,n) w1[i]=w1[i-1]+(!b[i]); 30 per(i,n,1) w2[i]=w2[i+1]+(!b[i]); 31 per(i,n,1) if (~a[i]) 32 tmp=(tmp+que(a[i]-1))%mod,add(a[i],1); 33 if (!tot){ printf("%d\n",tmp); return 0; } 34 fac[0]=1; fz=0; 35 rep(i,1,tot) fac[i]=1ll*fac[i-1]*i%mod; 36 rep(i,2,tot) fz=(1ll*fz*i%mod+1ll*fac[i]*(i-1)%mod*i2%mod)%mod; 37 per(i,n,1) if (~a[i]) s1=(s1+w2[a[i]])%mod; else d1=(d1+s1)%mod; 38 rep(i,1,n) if (~a[i]) s2=(s2+w1[a[i]])%mod; else d2=(d2+s2)%mod; 39 ans=((1ll*(d1+d2)*fac[tot-1]%mod+fz)%mod*ksm(fac[tot],mod-2)%mod+tmp)%mod; 40 printf("%d\n",ans); 41 return 0; 42 }
G:求有多少n位数(n为偶数)满足前n/2位之和等于后n/2位之和,且只能出现给定的k的数字(1<=k<=10),允许前导0。1e5
首先一个显然的背包DP模型,发现是一个卷积的形式,于是由卷积的结合律,我们可以倍增+FFT解决。常数过大TLE。
注意到化为点值表达之后可以直接做任意次乘法,于是化为点值表达后快速幂,再IDFT回去即可。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 using namespace std; 5 6 const int N=2000010,mod=998244353,G=3; 7 int n,m,x,ans,L,a[N],rev[N]; 8 9 int ksm(int a,int b){ 10 int res=1; 11 for (; b; a=1ll*a*a%mod,b>>=1) 12 if (b & 1) res=1ll*res*a%mod; 13 return res; 14 } 15 16 void NTT(int a[],int f){ 17 for (int i=0; i<n; i++) if (i<rev[i]) swap(a[i],a[rev[i]]); 18 for (int i=1; i<n; i<<=1){ 19 int wn=ksm(G,(f==1) ? (mod-1)/(i<<1) : (mod-1)-(mod-1)/(i<<1)); 20 for (int p=i<<1,j=0; j<n; j+=p){ 21 int w=1; 22 for (int k=0; k<i; k++,w=1ll*w*wn%mod){ 23 int x=a[j+k],y=1ll*w*a[i+j+k]%mod; a[j+k]=(x+y)%mod; a[i+j+k]=(x-y+mod)%mod; 24 } 25 } 26 } 27 if (f==1) return; 28 int inv=ksm(n,mod-2); 29 for (int i=0; i<n; i++) a[i]=1ll*a[i]*inv%mod; 30 } 31 32 int main(){ 33 freopen("g.in","r",stdin); 34 freopen("g.out","w",stdout); 35 scanf("%d%d",&n,&m); 36 rep(i,1,m) scanf("%d",&x),a[x]=1; 37 int nn=n,len=9*nn/2; 38 for (n=1; n<=len; n<<=1,L++); 39 for (int i=0; i<n; i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1)); 40 NTT(a,1); 41 for (int i=0; i<n; i++) a[i]=ksm(a[i],nn/2); 42 NTT(a,-1); 43 rep(i,0,len) ans=(ans+1ll*a[i]*a[i])%mod; 44 printf("%d\n",ans); 45 return 0; 46 }
AISING2019(Atcoder)(1.13)
C:H*W的图(H,W<=400),每个格子要么黑要么白,问有多少对点(s1,s2)满足,s1黑,s2白,且存在一条s1到s2的四连通路径,是路径上的点是黑白交错的。
可以发现,从一个点开始黑白交错地bfs,可以得到一个个连通块,同一连通块中的黑白点对间一定能找到一条符合条件的路径,不同连通块则一定不行。
E:一棵有正负点权的树,问最少分成多少个连通块,使每个连通块中,要么所有点均为正数,要么所有点权和为负数。
f[i][j]表示i子树中切j次,i所在连通块的权值最小能是多少(除了i所在连通块外均需要合法)。
g[i]表示i子树中,i所在连通块内均为正数,最少要切几次(除了i所在连通块也同样需要合法)。
转移显然,由于枚举上限可以改为size[i]和size[son],所以复杂度从$O(n^3)$降为$O(n^2)$
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 #define For(i,x) for (int i=h[x],k; i; i=nxt[i]) 6 typedef long long ll; 7 using namespace std; 8 9 const int N=5010; 10 int n,cnt,u,v,ans,a[N],g[N],sz[N],h[N],to[N<<1],nxt[N<<1]; 11 ll f[N][N]; 12 void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } 13 14 void dfs(int x,int fa){ 15 For(i,x) if ((k=to[i])!=fa) dfs(k,x); 16 if (a[x]>0){ 17 g[x]=0; 18 For(i,x) if ((k=to[i])!=fa){ 19 int s=g[k]; 20 rep(j,0,n) if (f[k][j]<0){ s=min(s,j+1); break; } 21 g[x]+=s; 22 } 23 } 24 f[x][0]=a[x]; sz[x]=1; 25 For(i,x) if ((k=to[i])!=fa){ 26 for (int j=sz[x]+sz[k]; ~j; j--){ 27 f[x][j]+=f[k][0]; 28 if (j-g[k]-1>=0) f[x][j]=min(f[x][j],f[x][j-g[k]-1]); 29 rep(s,max(0,j-sz[x]),min(j,sz[k])){ 30 if (s) f[x][j]=min(f[x][j],f[x][j-s]+f[k][s]); 31 if (s<j && f[k][s]<0) f[x][j]=min(f[x][j],f[x][j-s-1]); 32 } 33 } 34 sz[x]+=sz[k]; 35 } 36 } 37 38 int main(){ 39 freopen("e.in","r",stdin); 40 freopen("e.out","w",stdout); 41 scanf("%d",&n); 42 rep(i,1,n) scanf("%d",&a[i]); 43 rep(i,2,n) scanf("%d%d",&u,&v),add(u,v),add(v,u); 44 memset(g,42,sizeof(g)); memset(f,42,sizeof(f)); 45 dfs(1,1); ans=g[1]; 46 rep(i,0,n) if (f[1][i]<0){ ans=min(ans,i); break; } 47 printf("%d\n",ans); 48 return 0; 49 }
CFR532(div 2)(1.14)
E:一张边权有向图,将一些边反向是图中不存在环,最小化被反向边的权值最大值,输出方案。
显然二分答案,可以发现若所有>mid的边不构成环则一定可行。由于没有要求反转边数最少,所有边都从拓扑序小的往大的连即可。
注意所有边都要反转与所有边都不需要反转的情况。
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 6 #define For(i,x) for (int i=h[x],k; i; i=nxt[i]) 7 typedef long long ll; 8 using namespace std; 9 10 const int N=200010; 11 bool rev[N]; 12 int n,m,u,v,w,cnt,tot,tot1,L,R; 13 int h[N],d[N],q[N],q1[N],pos[N],to[N<<1],nxt[N<<1],val[N<<1]; 14 struct E{ int u,v,w; }e[N<<1]; 15 void add(int u,int v,int w){ to[++cnt]=v; nxt[cnt]=h[u]; val[cnt]=w; h[u]=cnt; } 16 17 int main(){ 18 freopen("e.in","r",stdin); 19 freopen("e.out","w",stdout); 20 scanf("%d%d",&n,&m); 21 rep(i,1,m) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w),R=max(R,e[i].w); 22 rep(i,1,n) q1[i]=i; 23 while (L<R){ 24 int mid=(L+R)>>1; 25 rep(i,1,n) h[i]=d[i]=0; cnt=tot=0; 26 rep(i,1,m) if (e[i].w>mid) add(e[i].u,e[i].v,i),d[e[i].v]++; 27 int st=0,ed=0; 28 rep(i,1,n) if (!d[i]) q[++ed]=i; 29 while (st<ed){ 30 int x=q[++st]; 31 For(i,x){ 32 d[k=to[i]]--; 33 if (!d[k]) q[++ed]=k; 34 } 35 } 36 if (ed==n){ 37 R=mid; 38 rep(i,1,m) q1[i]=q[i]; 39 }else L=mid+1; 40 } 41 if (!L){ puts("0 0"); return 0; } 42 int res=0; 43 rep(i,1,n) pos[q1[i]]=i; 44 rep(i,1,m) if (pos[e[i].u]>pos[e[i].v]) rev[i]=1,res++; 45 printf("%d %d\n",L,res); 46 rep(i,1,m) if (rev[i]) printf("%d ",i); 47 return 0; 48 }
F:区间最大子集异或和。
方法一:最显然的线性基+线段树/ST表。前者$O(n\log^2 v+n\log^3 v)$后者$O(n\log^3 v+n\log^2 v)$
方法二:这里的第4题,分治+线性基,每次只处理跨越分治区间中点的询问。从中点开始往前维护一个后缀和,往后维护一个前缀和,每次询问使用线性基合并即可。由于每层维护前缀和复杂度$O(n\log n)$,共$O(\log n)$层;每个询问都只会使用一次$O(\log^2 n)$的线性基合并。故总复杂度为$O((n+q)\log^2 v)$
方法三:将询问按右端点排序,每次查询只查询线性基中插入位置>=l的基。当插入一个新基时,若当前位置没有基则直接放入并标记上时间戳;若当前位置有基且此基时间戳在待插入基时间戳之前,则将原基替换为新基,并将原基继续下放重复插入操作(具体看代码)。
考虑这个算法的正确性,首先每次操作都不会改变张成的现行空间,且不会改变每位上的相互捆绑的限制,所以不会出现错误。
复杂度:$O(n\log v)$
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define ls (x<<1) 6 #define rs (ls|1) 7 #define lson ls,L,mid 8 #define rson rs,mid+1,R 9 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 10 typedef long long ll; 11 using namespace std; 12 13 const int N=500010,S=(1<<24)-1; 14 int n,Q,l,r,a[N]; 15 struct D{ int s[23]; D(){ memset(s,0,sizeof(s)); } }v[N<<2]; 16 17 void add(D &a,int x){ 18 for (int i=22; ~i; i--) 19 if (x&(1<<i)){ 20 if (!a.s[i]) { a.s[i]=x; break; } else x^=a.s[i]; 21 } 22 } 23 24 D uni(D a,D b){ 25 D c; 26 rep(i,0,22) c.s[i]=a.s[i]; 27 rep(i,0,22) add(c,b.s[i]); 28 return c; 29 } 30 31 int qmn(D a,int x){ 32 for (int i=22; ~i; i--) 33 if ((x^a.s[i])<x) x^=a.s[i]; 34 return x; 35 } 36 37 void build(int x,int L,int R){ 38 if (L==R){ add(v[x],a[L]); return; } 39 int mid=(L+R)>>1; 40 build(lson); build(rson); 41 v[x]=uni(v[ls],v[rs]); 42 } 43 44 D que(int x,int L,int R,int l,int r){ 45 if (L==l && r==R) return v[x]; 46 int mid=(L+R)>>1; 47 if (r<=mid) return que(lson,l,r); 48 else if (l>mid) return que(rson,l,r); 49 else return uni(que(lson,l,mid),que(rson,mid+1,r)); 50 } 51 52 int main(){ 53 scanf("%d",&n); 54 rep(i,1,n) scanf("%d",&a[i]); 55 build(1,1,n); 56 for (scanf("%d",&Q); Q--; ){ 57 scanf("%d%d",&l,&r); D c=que(1,1,n,l,r); 58 printf("%d\n",S-qmn(c,S)); 59 } 60 return 0; 61 }
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 6 typedef long long ll; 7 using namespace std; 8 9 const int N=500010; 10 int n,q,l,r,a[N],ans[N]; 11 struct P{ int l,r,i; }Q[N],u[N]; 12 struct D{ 13 int s[23]; 14 D(){ memset(s,0,sizeof(s)); } 15 D(const D &a){ memcpy(s,a.s,sizeof(a.s)); } 16 }pre[N],suf[N]; 17 18 void add(D &a,int x){ 19 for (int i=22; ~i; i--) 20 if (x&(1<<i)){ 21 if (!a.s[i]) { a.s[i]=x; break; } else x^=a.s[i]; 22 } 23 } 24 25 D uni(D a,D b){ 26 D c; 27 rep(i,0,22) c.s[i]=a.s[i]; 28 rep(i,0,22) add(c,b.s[i]); 29 return c; 30 } 31 32 int qmx(D a){ 33 int res=0; 34 for (int i=22; ~i; i--) 35 if ((res^a.s[i])>res) res^=a.s[i]; 36 return res; 37 } 38 39 void solve(int l,int r,int low,int high){ 40 if (l>r || low>high) return; 41 if (low==high){ 42 rep(i,l,r) ans[Q[i].i]=a[low]; 43 return; 44 } 45 int mid=(low+high)>>1; 46 for (int i=mid;i>=low;i--){ 47 if (i==mid) pre[i]=pre[0]; else pre[i]=pre[i+1]; 48 add(pre[i],a[i]); 49 } 50 for (int i=mid+1;i<=high;i++){ 51 if (i==mid+1) suf[i]=pre[0]; else suf[i]=suf[i-1]; 52 add(suf[i],a[i]); 53 } 54 int head=l-1,tail=r+1; 55 rep(i,l,r) if (Q[i].l<=mid && Q[i].r>mid) 56 ans[Q[i].i]=qmx(uni(pre[Q[i].l],suf[Q[i].r])); 57 else if (Q[i].r<=mid) u[++head]=Q[i]; else u[--tail]=Q[i]; 58 rep(i,l,r) Q[i]=u[i]; 59 solve(l,head,low,mid); 60 solve(tail,r,mid+1,high); 61 } 62 63 int main(){ 64 scanf("%d",&n); 65 rep(i,1,n) scanf("%d",&a[i]); 66 scanf("%d",&q); 67 rep(i,1,q) scanf("%d%d",&Q[i].l,&Q[i].r),Q[i].i=i; 68 solve(1,q,1,n); 69 rep(i,1,q) printf("%d\n",ans[i]); 70 return 0; 71 }
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 6 typedef long long ll; 7 using namespace std; 8 9 const int N=500010; 10 int n,q,l,r,a[N],ans[N]; 11 struct P{ int l,r,id; }Q[N]; 12 bool operator <(const P &a,const P &b){ return a.r<b.r; } 13 struct D{ int s[23],p[23]; }s; 14 15 void add(D &a,int x,int p){ 16 for (int i=22; ~i; i--) 17 if (x&(1<<i)){ 18 if (!a.s[i]) { a.s[i]=x; a.p[i]=p; break; } 19 if (a.p[i]<p) swap(a.p[i],p),swap(a.s[i],x); 20 x^=a.s[i]; 21 } 22 } 23 24 int que(D &a,int l){ 25 int res=0; 26 for (int i=22; ~i; i--) 27 if (a.p[i]>=l && (res^a.s[i])>res) res^=a.s[i]; 28 return res; 29 } 30 31 int main(){ 32 freopen("f.in","r",stdin); 33 freopen("f.out","w",stdout); 34 scanf("%d",&n); 35 rep(i,1,n) scanf("%d",&a[i]); 36 scanf("%d",&q); 37 rep(i,1,q) scanf("%d%d",&Q[i].l,&Q[i].r),Q[i].id=i; 38 sort(Q+1,Q+q+1); int r=0; 39 rep(i,1,q){ 40 while (r<=Q[i].r) add(s,a[r],r),r++; 41 ans[Q[i].id]=que(s,Q[i].l); 42 } 43 rep(i,1,q) printf("%d\n",ans[i]); 44 return 0; 45 }
CGR1(div 1+2)(2.7)
D:给1e6个花色,求最多能'碰'或'吃'多少次。发现同样的顺子最多吃两次,于是f[i][j][k]表示前i个,[i-1,i,i+1]的顺子吃了j个,[i,i+1,i+2]的顺子吃了k个。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 using namespace std; 6 7 const int N=1000010; 8 int n,m,x,s[N],f[N][3][3]; 9 10 int main(){ 11 scanf("%d%d",&n,&m); 12 rep(i,1,n) scanf("%d",&x),s[x]++; 13 memset(f,-1,sizeof(f)); f[0][0][0]=0; 14 rep(i,0,m-1) rep(j,0,2) rep(k,0,2) if (~f[i][j][k] && j+k<=s[i+1]) 15 rep(l,0,min(2,s[i+1]-j-k)) 16 f[i+1][k][l]=max(f[i+1][k][l],f[i][j][k]+l+(s[i+1]-j-k-l)/3); 17 printf("%d\n",f[m][0][0]); 18 return 0; 19 }
E:一个数列,每次可以将一个数a[1<i<n]变为a[i-1]+a[i+1]-a[i]。问能否通过操作将a[]变为b[]。(1e6)
发现这个操作就是交换差分数组的相邻两项,于是将两个差分数组排序一一比较,再比较a[1]和b[1]即可。
F:Q次询问x,l,r,求x到DFS序在[l,r]中的所有叶子的距离最小值。(n,Q<=5e5)
离线将所有询问存储在对应节点上。线段树维护当前点到所有点的距离,按DFS顺序转移。
1 #include<cstdio> 2 #include<vector> 3 #include<algorithm> 4 #include<iostream> 5 #define ls (x<<1) 6 #define rs (ls|1) 7 #define lson ls,L,mid 8 #define rson rs,mid+1,R 9 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 10 #define For(i,x) for (int i=h[x],k; i; i=nxt[i]) 11 typedef long long ll; 12 using namespace std; 13 14 const int N=1000010; 15 const ll inf=1e15; 16 int n,Q,w,cnt,x,b[N],R[N],h[N],to[N<<1],nxt[N<<1],val[N<<1]; 17 ll ans[N],d[N],mn[N<<2],tag[N<<2]; 18 struct Que{ int x,l,r; }q[N]; 19 vector<int>ve[N]; 20 21 void add(int u,int v,int w){ to[++cnt]=v; nxt[cnt]=h[u]; val[cnt]=w; h[u]=cnt; } 22 23 void dfs(int x){ 24 R[x]=x; 25 For(i,x) d[k=to[i]]=d[x]+val[i],dfs(k),R[x]=max(R[x],R[k]); 26 } 27 28 void push(int x){ 29 tag[ls]+=tag[x]; mn[ls]+=tag[x]; 30 tag[rs]+=tag[x]; mn[rs]+=tag[x]; tag[x]=0; 31 } 32 33 void build(int x,int L,int R){ 34 if (L==R){ mn[x]=b[L]?inf:d[L]; return; } 35 int mid=(L+R)>>1; 36 build(lson); build(rson); mn[x]=min(mn[ls],mn[rs]); 37 } 38 39 void mdf(int x,int L,int R,int l,int r,int k){ 40 if (L==l && r==R){ tag[x]+=k; mn[x]+=k; return; } 41 int mid=(L+R)>>1; push(x); 42 if (r<=mid) mdf(lson,l,r,k); 43 else if (l>mid) mdf(rson,l,r,k); 44 else mdf(lson,l,mid,k),mdf(rson,mid+1,r,k); 45 mn[x]=min(mn[ls],mn[rs]); 46 } 47 48 ll que(int x,int L,int R,int l,int r){ 49 if (L==l && r==R) return mn[x]; 50 int mid=(L+R)>>1; push(x); 51 if (r<=mid) return que(lson,l,r); 52 else if (l>mid) return que(rson,l,r); 53 else return min(que(lson,l,mid),que(rson,mid+1,r)); 54 } 55 56 void solve(int x){ 57 int ed=ve[x].size()-1,t; 58 rep(i,0,ed) t=ve[x][i],ans[t]=que(1,1,n,q[t].l,q[t].r); 59 For(i,x){ 60 k=to[i]; 61 mdf(1,1,n,1,n,val[i]); 62 mdf(1,1,n,k,R[k],-2*val[i]); 63 solve(k); 64 mdf(1,1,n,1,n,-val[i]); 65 mdf(1,1,n,k,R[k],2*val[i]); 66 } 67 } 68 69 int main(){ 70 freopen("f.in","r",stdin); 71 freopen("f.out","w",stdout); 72 scanf("%d%d",&n,&Q); 73 rep(i,2,n) scanf("%d%d",&x,&w),add(x,i,w),b[x]=1; 74 dfs(1); build(1,1,n); 75 rep(i,1,Q) scanf("%d%d%d",&q[i].x,&q[i].l,&q[i].r),ve[q[i].x].push_back(i); 76 solve(1); 77 rep(i,1,Q) cout<<ans[i]<<endl; 78 return 0; 79 }
CFR537(div 2)(2.9)(VP)
D:一个长(<=1e5)为偶数的序列,可以随意交换两块颜色(颜色数<=52),要求所有同色块必须都在序列的前半段或后半段(哪半段随意)。
再给你k个询问,每次询问给出两个颜色,要求这两个颜色块也必须在同一半段,求方案数。
一个比较显然的DP是,f[i][j]表示前i种颜色在前半段中占用了j的长度的方案数,每次乘上组合数来计算顺序。同时本质不同的询问只有k^2种,所以复杂度$O(nk^3)$。
考虑优化,每次询问相当于撤销两个块的代价,合并这两个块后再放回去,但上式不容易实现撤回操作。
注意到另一个问题,如果半段之间颜色的位置先后不影响的话,就是一个01背包问题。而当我们固定每种颜色在哪半段后,最后的有序方案数所要乘的组合数都是$\frac{n!}{\prod cnt_{i}!}$。而01背包的DP是非常容易撤回的,于是问题就解决了。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 typedef long long ll; 6 using namespace std; 7 8 const int N=100010,mod=1e9+7; 9 char s[N]; 10 int n,Q,u,v,cnt[55],dp[N],f[N],fac[N],inv[N],ans[55][55]; 11 12 int id(char c){ return ('A'<=c && c<='Z') ? c-'A' : c-'a'+26; } 13 14 int ksm(int a,int b){ 15 int res=1; 16 for (; b; a=1ll*a*a%mod,b>>=1) 17 if (b & 1) res=1ll*res*a%mod; 18 return res; 19 } 20 21 int main(){ 22 freopen("d.in","r",stdin); 23 freopen("d.out","w",stdout); 24 scanf("%s%d",s+1,&Q); n=strlen(s+1); 25 rep(i,1,n) cnt[id(s[i])]++; 26 fac[0]=1; 27 rep(i,1,n) fac[i]=1ll*fac[i-1]*i%mod; 28 inv[n]=ksm(fac[n],mod-2); 29 for (int i=n-1; ~i; i--) inv[i]=1ll*inv[i+1]*(i+1)%mod; 30 dp[0]=1; 31 rep(i,0,51) if (cnt[i]) 32 for (int j=n/2; j>=cnt[i]; j--) dp[j]=(dp[j]+dp[j-cnt[i]])%mod; 33 int v=1ll*fac[n/2]*fac[n/2]%mod; 34 rep(i,0,51) v=1ll*v*inv[cnt[i]]%mod; 35 rep(x,0,51) rep(y,0,51){ 36 rep(i,0,n/2) f[i]=dp[i]; 37 rep(i,cnt[x],n/2) f[i]=(f[i]-f[i-cnt[x]]+mod)%mod; 38 if (x!=y) rep(i,cnt[y],n/2) f[i]=(f[i]-f[i-cnt[y]]+mod)%mod; 39 ans[x][y]=2ll*f[n/2]*v%mod; 40 } 41 while (Q--) scanf("%d%d",&u,&v),printf("%d\n",ans[id(s[u])][id(s[v])]); 42 return 0; 43 }
E:n个点的数,每次询问给出k,m,r和a[k],表示要求在这个数以r为根时,将a[]中的k个点分成至多m个集合,满足一个集合中任意两个点不存在祖先-子孙关系。(n,Q<=1e5,sum{k}<=1e5,m<=min(300,k))
显然虚树,但直接DP想做到O(nm)比较困难,于是我们按dfs序或bfs序DP,每次每个点的可选集合数就非常显然了。非常巧妙。
另外这题写虚树在换根等方面会比较麻烦,由于需要知道的只有两点之间关键点的个数,所以可以树剖+BIT实现。
复杂度$O(n\log^2n)$,多了两个log却跑得非常快,榜上用时前五。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 #define For(i,x) for (int i=h[x],k; i; i=nxt[i]) 5 using namespace std; 6 7 const int N=200010,mod=1e9+7; 8 int n,Q,u,v,k,m,r,ans,tim,b[N],f[N],fa[N],sz[N]; 9 int c[N],a[N],son[N],dep[N],pos[N],top[N]; 10 int cnt,h[N],to[N<<1],nxt[N<<1],val[N<<1]; 11 void ins(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } 12 13 void dfs(int x){ 14 dep[x]=dep[fa[x]]+1; sz[x]=1; 15 For(i,x) if ((k=to[i])!=fa[x]){ 16 fa[k]=x; dfs(k); sz[x]+=sz[k]; 17 if (sz[k]>sz[son[x]]) son[x]=k; 18 } 19 } 20 21 void dfs2(int x,int tp){ 22 pos[x]=++tim; top[x]=tp; 23 if (son[x]) dfs2(son[x],tp); 24 For(i,x) if ((k=to[i])!=fa[x] && k!=son[x]) dfs2(k,k); 25 } 26 27 int lca(int x,int y){ 28 for (; top[x]!=top[y]; x=fa[top[x]]) 29 if (dep[top[x]]<dep[top[y]]) swap(x,y); 30 return dep[x]<dep[y] ? x : y; 31 } 32 33 void add(int x,int k){ for (; x<=n; x+=x&-x) c[x]+=k; } 34 int que(int x){ int res=0; for (; x; x-=x&-x) res+=c[x]; return res; } 35 36 int ask(int x,int y){ 37 int res=0; 38 for (; top[x]!=top[y]; x=fa[top[x]]){ 39 if (dep[top[x]]<dep[top[y]]) swap(x,y); 40 res+=que(pos[x])-que(pos[top[x]]-1); 41 } 42 if (pos[x]<pos[y]) swap(x,y); 43 res+=que(pos[x])-que(pos[y]-1); 44 return res; 45 } 46 47 int main(){ 48 scanf("%d%d",&n,&Q); 49 rep(i,2,n) scanf("%d%d",&u,&v),ins(u,v),ins(v,u); 50 dfs(1); dfs2(1,1); 51 while (Q--){ 52 scanf("%d%d%d",&k,&m,&r); ans=0; 53 rep(i,1,k) scanf("%d",&b[i]),add(pos[b[i]],1); 54 rep(i,1,k) a[i]=ask(b[i],r)-1; 55 sort(a+1,a+k+1); bool flag=0; 56 rep(i,1,k) if (a[i]>=m){ flag=1; break; } 57 if (flag){ 58 rep(i,1,k) add(pos[b[i]],-1); 59 puts("0"); continue; 60 } 61 rep(i,1,m) f[i]=0; f[0]=1; 62 rep(i,1,k) for (int j=min(i,m); ~j; j--) 63 if (j<=a[i]) f[j]=0; else f[j]=(f[j-1]+1ll*f[j]*(j-a[i]))%mod; 64 rep(i,0,m) ans=(ans+f[i])%mod; 65 printf("%d\n",ans); 66 rep(i,1,k) add(pos[b[i]],-1); 67 } 68 return 0; 69 }
Yahoo Programming Contest 2019(Atcoder)(2.9)
D:一个数轴,任选一点开始,每次可以向左或向右移动一格,每当经过i-0.5时,cnt[i]++。再给你一个a[],求最后cnt[]与a[]所有对应元素差的绝对值之和。
观察发现,最终的cnt序列由“0 非零偶数 奇数 非零偶数 0”组成。其中每个部分长度任意(可以为0)。
于是DP或前缀和均可。
F:给出n个人手上的两个球,分别可能是两红、一红一蓝、两蓝。每轮所有有球的人都同时将自己手上的球选一个传给前一个。第一个人给到序列a[]末尾。求最后a[]可能的方案数。
显然若对任意位置i,前i个位置的红球总数不超过前min(i,n)个同学初始手上红球的个数和,则序列一定合法。于是dp[i][j]表示序列前i个有j个红球的方案数即可。
E:一个n*m的01矩阵,从中选一些行列,使交集形成的子矩阵中所有数的异或和为奇数,求选法数。
一个类似玛里苟斯中的结论:假设我们已经选出了行的集合,那么只要这些行整个异或起来不全为0,那么可选的列的集合就是2^(m-1)。因为n个数中选奇数和偶数的方案数是相等的。
于是问题变成求有多少行的子集使整体异或起来不全为0,也就是整个矩阵的秩,线性基即可。
CFR530(div 1)(2.12)(VP)
C:给你n(1e5)和s(1e10),构造一棵n个节点的树,使所有子树大小之和为s,且儿子最多的点儿子数最小。
显然所有子树大小之和=所有点深度之和,于是二分最多儿子数后很容易判定。
设二分的儿子数为k,则能构成的s最大值为一条链:n*(n+1)/2,最小值为拍成完全k叉树时的s,最大最小值之间的值也一定都能通过调整取到。
考虑如何调整,我们先构出完全k叉树,然后每次找到深度最大的包含不止一个点的一层,将其中一个没有孩子的点往下移一层即可。
可以通过一次挂到最底下加速。
1 #include<cstdio> 2 #include<algorithm> 3 #include<iostream> 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 typedef long long ll; 6 using namespace std; 7 8 const int N=100010; 9 ll n,s,a[N],d[N]; 10 11 bool chk(int k){ 12 ll tmp=1,c=1,sm=1; 13 rep(i,2,n){ 14 c*=k; sm+=min(n-tmp,c)*i; tmp+=c; 15 if (tmp>=n) break; 16 } 17 if (sm<=s) return 1; else return 0; 18 } 19 20 int main(){ 21 cin>>n>>s; 22 if (s<2*n-1 || s>n*(n+1)>>1){ puts("No"); return 0; } 23 puts("Yes"); int L=1,R=n-1; 24 while (L<R){ 25 int mid=(L+R)>>1; 26 if (chk(mid)) R=mid; else L=mid+1; 27 } 28 ll tmp=1,c=1,sm=1,k=0; a[1]=1; 29 rep(i,2,n){ 30 c*=L; a[i]=min(n-tmp,c); sm+=a[i]*i; tmp+=a[i]; 31 if (tmp>=n){ k=i; break; } 32 } 33 int p=k,x=1; s-=sm; 34 while (s){ 35 while (a[p]==1) p--; a[p]--; 36 if (k-p+1<=s) a[++k]=1,s-=k-p; else a[p+s]++,s=0; 37 } 38 rep(i,2,k){ 39 int tmp=x+a[i-1]; 40 rep(j,1,a[i]){ 41 printf("%d ",x); d[x]++; 42 if (d[x]==L) x++; 43 } 44 x=tmp; 45 } 46 return 0; 47 }
D:n个人各有一个战斗值,每次可以任选两个人战斗,若a[i]<=a[j]则i消失,a[j]+=a[i]。
当a[i]<=a[j]&a[j]<=2*a[i]时,称这次战斗是危险的。共战斗n-1次,问危险值最大能是多少。
同时可能会加入或删除一个人,在线查询答案。2e5
首先定义一个人是fat的,当且仅当这个人的战斗值比所有战斗值小于他的人的战斗值之和的两倍还要大。
可以证明:最后答案就是ans=n-fat的人个数。
答案显然最多为ans,考虑如何证明一定能取到ans。
每次选战斗值最小的两个人i和j(a[i]<=a[j])战斗,若这场战斗不危险,那么:
1.j一定之前没有参加过战斗。
2.a[i]一定是所有战斗力小于a[j]的人的战斗力之和。
3.由上可知,j是fat的。命题得证。
于是问题变为:在线加入、删除,在线查询有多少数满足:它比所有比它小的数之和的两倍还要大。
将值域分为[1,2),[2,4),[4,8),...,那么显然一块中至多有一个fat,且这个人一定是这块中战斗力最小的人。
于是对每块维护块内和、块内最小值即可。$O(n\log n)$
1 #include<set> 2 #include<cstdio> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 typedef long long ll; 6 using namespace std; 7 8 char ch; 9 int Q,x,n,ans,mn[32]; 10 ll sum[32]; 11 multiset<int>S[32]; 12 13 int main(){ 14 for (scanf("%d",&Q); Q--; ){ 15 scanf(" %c%d",&ch,&x); int k=0; 16 for (; (1<<(k+1))<=x; k++); 17 if (ch=='+') S[k].insert(x),n++,sum[k]+=x; else S[k].erase(S[k].find(x)),n--,sum[k]-=x; 18 mn[k]=*S[k].begin(); ll sm=0; ans=n; 19 rep(i,0,30){ 20 if (S[i].begin()!=S[i].end()) ans-=(mn[i]>2*sm); 21 sm+=sum[i]; 22 } 23 printf("%d\n",ans); 24 } 25 return 0; 26 }
CFR541(div 2)(2.23)
C:n个数排一圈使相邻两数差的最大值最小。1e5。
排序后奇数位全放前面,偶数位反序后放后面。形成的单峰序列一定最优,最大差为所有a[i+2]-a[i]的最大值。
考虑证明,就是在一个完全图中找一条哈密顿回路。找到最大的a[i+1]-a[i-1],并将所有跨过a[i]的边删去,发现图已不存在哈密顿回路。
E:定义s*t为t+s[1]+t+s[2]+t+...+t+s[n]+t,给n个字符串求s1*s2*s3...*sn的最长同字符子串。
f[i][j]表示前i个字符串的乘积中字符j的最长同字符子串,转移显然。
1 #include<iostream> 2 using namespace std; 3 int f[200010][30];string s; 4 int main() 5 { 6 int n,i,j,ans=0;cin>>n; 7 for (i=1;i<=n;i++) 8 { 9 cin>>s;int l=s.length(),t[30]={},r[30]={}; 10 for (j=0;j<l;j++) 11 { 12 if (j && s[j-1]==s[j]) t[s[j]-'a']++;else t[s[j]-'a']=1; 13 r[s[j]-'a']=max(r[s[j]-'a'],t[s[j]-'a']); 14 } 15 for (j=0;j<26;j++) 16 { 17 f[i][j]=r[j]; 18 int t1=0,t2=l-1;while (s[t1]=='a'+j && t1<l) t1++; 19 while (s[t2]=='a'+j && t2>t1) t2--; 20 if (t1==l) f[i][j]=max(f[i][j],f[i-1][j]*l+l+f[i-1][j]); 21 else if (f[i-1][j]) f[i][j]=max(f[i][j],t1+l-t2); 22 if (i==n)ans=max(ans,f[i][j]); 23 } 24 } 25 cout<<ans; 26 }
CFR542(2.25)
F:树上每个点对之间路径权值异或和放入一个数组(共n^2个数),求第k小。
首先转成每个点到根路径异或和,然后变成一个n个数的数组中任选两点异或求第k小。(n<=1e6,a[i]<2^62,256M)
显然可以建Trie,但空间不够,于是可以想到隐式建Trie,即排序后二分子树左右端点,两个log时间不够。
发现这样是有浪费的,因为贪心每位时只需要向下走一步即可。见代码。
1 #include<cstdio> 2 #include<algorithm> 3 #include<iostream> 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 typedef long long ll; 6 using namespace std; 7 8 const int N=2000010; 9 int n,tot,t,p,a[N],b[N],ch[N][2],sz[N]; 10 ll k,s,ans,v[N]; 11 12 int go(int a,int b){ return ch[a][b]?ch[a][b]:ch[a][b]=++tot; } 13 14 int main(){ 15 cin>>n>>k; 16 rep(i,2,n) cin>>p>>v[i],v[i]^=v[p]; 17 rep(i,1,n) a[i]=b[i]=1;//v[i]和(v[i]在这一位取反)目前在字典树中的位置 18 for(int j=61; ~j; j--){ 19 rep(i,1,tot) ch[i][0]=ch[i][1]=sz[i]=0; s=tot=t=0; 20 rep(i,1,n) sz[a[i]=go(a[i],v[i]>>j&1)]++;//往下走一层 21 rep(i,1,n) s+=sz[ch[b[i]][v[i]>>j&1]];//计算这一位不取反的贡献 22 if (s<k) ans|=1ll<<j,k-=s,t=1; 23 rep(i,1,n) b[i]=ch[b[i]][(v[i]>>j&1)^t]; 24 } 25 cout<<ans<<endl; 26 return 0; 27 }
AGC031(3.16)
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 5 int n,a,b; 6 7 void F(int a,int b,int v){ 8 if(a==b){ printf("%d ",a); return; } 9 int x=(a^b)&(-(a^b)); v^=x; int y=v&(-v); 10 F(a,a^y,v); F(a^y^x,b,v); 11 } 12 13 int main(){ 14 freopen("c.in","r",stdin); 15 freopen("c.out","w",stdout); 16 scanf("%d%d%d",&n,&a,&b); 17 if(__builtin_popcount(a^b)&1) puts("YES"),F(a,b,(1<<n)-1); else puts("NO"); 18 return 0; 19 }
CFR546(div 2)(3.21)
E:https://blog.csdn.net/qq_39599067/article/details/88428309
1 #include<cstdio> 2 #include<algorithm> 3 #include<iostream> 4 #define ls (x<<1) 5 #define rs (ls|1) 6 #define lson ls,L,mid 7 #define rson rs,mid+1,R 8 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 9 typedef long long ll; 10 using namespace std; 11 12 const int N=100010; 13 const ll inf=1e17; 14 char op; 15 ll y,a[N],k[N],b[N],t[N],tag[N<<2],sm[N<<2]; 16 int n,Q,x; 17 18 void push(int x,int L,int R){ 19 int mid=(L+R)>>1; 20 if (tag[x]!=inf){ 21 sm[ls]=tag[x]*(mid-L+1); sm[rs]=tag[x]*(R-mid); 22 tag[ls]=tag[rs]=tag[x]; tag[x]=inf; 23 } 24 } 25 26 void build(int x,int L,int R){ 27 int mid=(L+R)>>1; tag[x]=inf; 28 if (L==R){ sm[x]=b[L]; return; } 29 build(lson); build(rson); sm[x]=sm[ls]+sm[rs]; 30 } 31 32 void mdf(int x,int L,int R,int l,int r,ll k){ 33 if (L==l && r==R){ tag[x]=k; sm[x]=k*(R-L+1); return; } 34 int mid=(L+R)>>1; push(x,L,R); 35 if (r<=mid) mdf(lson,l,r,k); 36 else if (l>mid) mdf(rson,l,r,k); 37 else mdf(lson,l,mid,k),mdf(rson,mid+1,r,k); 38 sm[x]=sm[ls]+sm[rs]; 39 } 40 41 ll que(int x,int L,int R,int l,int r){ 42 if (L==l && r==R) return sm[x]; 43 int mid=(L+R)>>1; push(x,L,R); 44 if (r<=mid) return que(lson,l,r); 45 else if (l>mid) return que(rson,l,r); 46 else return que(lson,l,mid)+que(rson,mid+1,r); 47 } 48 49 int main(){ 50 freopen("e.in","r",stdin); 51 freopen("e.out","w",stdout); 52 scanf("%d",&n); 53 rep(i,1,n) cin>>a[i]; 54 rep(i,1,n-1) cin>>k[i],k[i]+=k[i-1],t[i]=t[i-1]+k[i],b[i]=a[i]-k[i-1]; 55 b[n]=a[n]-k[n-1]; build(1,1,n); 56 for (scanf("%d",&Q); Q--; ){ 57 scanf(" %c",&op); cin>>x>>y; 58 if (op=='s') cout<<que(1,1,n,x,y)+t[y-1]-(x>=2 ? t[x-2]: 0)<<endl; 59 else{ 60 int l=x,r=n; ll res=l,tp=que(1,1,n,x,x)+y; 61 while (l<=r){ 62 int mid=(l+r)>>1; 63 if (que(1,1,n,mid,mid)>=tp) r=mid-1; 64 else res=mid,l=mid+1; 65 } 66 mdf(1,1,n,x,res,tp); 67 } 68 } 69 return 0; 70 }
CFR551(div 2)(4.14)
F:一个长为l的线段上随机选n条线段,求被至少k条线段覆盖的区间长度和的期望。(n,k<=2000,l<=1e9)
一种方法是积分,答案就是$l\times \sum_{i=k}^{n}C_n^i\int_{0}^{1}[2x(1-x)]^i[x^2+(1-x)^2]^{n-i}dx$,暴力多项式展开后积分即可。
另一种方法是DP,首先可以发现,一段区间的长度期望是$\frac{l}{2n+1}$的,那么只要求出期望有几段区间被至少k条线段覆盖即可。
f[i][j]表示已考虑前i个端点,有j个左端点还未匹配到右端点的方案数。然后对于每个区间就可以快速求出有多少种方案中它的出现次数不少于k次了。
因为求的是期望所以答案记得除以f[2n][0]。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 using namespace std; 5 6 const int N=4500,mod=998244353; 7 int n,k,l,ans,f[N][N],fac[N]; 8 9 int ksm(int a,int b){ 10 int res=1; 11 for (; b; a=1ll*a*a%mod,b>>=1) 12 if (b & 1) res=1ll*res*a%mod; 13 return res; 14 } 15 16 int main(){ 17 freopen("f.in","r",stdin); 18 freopen("f.out","w",stdout); 19 scanf("%d%d%d",&n,&k,&l); f[0][0]=1; fac[0]=1; 20 rep(i,1,n) fac[i]=1ll*fac[i-1]*i%mod; 21 rep(i,0,2*n) rep(j,0,min(n,i)) if (f[i][j]){ 22 f[i+1][j+1]=(f[i+1][j+1]+f[i][j])%mod; 23 if (j) f[i+1][j-1]=(f[i+1][j-1]+1ll*f[i][j]*j)%mod; 24 } 25 rep(i,1,2*n) rep(j,k,min(n,i)) ans=(ans+1ll*f[i][j]*f[2*n-i][j]%mod*fac[j])%mod; 26 ans=1ll*ans*l%mod*ksm(2*n+1,mod-2)%mod*ksm(f[2*n][0],mod-2)%mod; 27 printf("%d\n",ans); 28 return 0; 29 }