Codeforces乱刷集合
发这篇博客的目的是因为刷了些水题,但又有一些不错的地方可以加以借鉴....然后又不想一个一个发....
Codeforces731A
题目大意:给出一个26个字母的环,初始指向a,可以顺时针转或者逆时针转,给出一个字符串求最少转几次能得到他。
题解:傻逼模拟。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; char S[100000]; long long ans; int main() { scanf("%s",S+1); int len=strlen(S+1); int now,last,a; last='a'; for (int i=1; i<=len; i++) { now=S[i]; if (now>last) a=now-last; else a=last-now; ans+=min(a,26-a); last=now; } printf("%lld\n",ans); return 0; }
Codeforces731B
题目大意:给出一个序列,你可以进行两种操作,1.一个位置-2;2.相邻两个位置-1;问是否可以恰好使所有数减到0。
题解:贪心,因为最左边的相邻的只有它右边一个,所以可以从最左边的开始搞,注意不会减到0即可。
#include<cstdio> #include<iostream> #include<cstring> using namespace std; #define N 200005 inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } int n,a[N],f[N]; int main() { n=read(); for(int i=1;i<=n;i++) a[i]=read(); a[1]%=2; for(int i=1;i<=n;i++){ a[i]-=a[i-1]; if(a[i] < 0) { puts("NO"); return 0; } a[i]%=2; } if(a[n]) puts("NO"); else puts("YES"); return 0; }
Codeforces731C
题目大意:给出一个序列,每个位置有一个颜色,再给出一些区间要求这些区间中的颜色必须全都一样,求最少需要改变这个序列中的多少个位置,能使所有区间满足。
题解:可以用并查集搞一下,或者用连通分支搞一下。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } int N,M,K,size,maxx,ans; struct data{int l,r,f;}a[200010]; bool cmp(data x,data y) {return x.l==y.l? x.r<y.r : x.l<y.l;} struct EdgeNode{int next,to;}edge[400010]; int head[200010],cnt=1; int st[200010],top; inline void AddEdge(int u,int v) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v;} inline void InsertEdge(int u,int v) {AddEdge(u,v); AddEdge(v,u);} bool visit[200010]; int num[200010],c[200010]; void work(int x) { /*printf("%d\n",x);*/ visit[x]=1; size++; num[c[x]]++; st[++top]=x; if (num[c[x]]>maxx) maxx=num[c[x]]; for (int i=head[x]; i; i=edge[i].next) if (!visit[edge[i].to]) work(edge[i].to); } void clear() { for (int i=1; i<=top; i++) num[c[st[i]]]--; top=0; } int main() { N=read(),M=read(),K=read(); for (int i=1; i<=N; i++) c[i]=read(); for (int i=1; i<=M; i++) a[i].l=read(),a[i].r=read(),InsertEdge(a[i].l,a[i].r); for (int i=1; i<=N; i++) if (!visit[i]) maxx=0,size=0,work(i),ans+=size-maxx,clear();/*,printf("a %d\n",i)*/ printf("%d\n",ans); return 0; }
Codeforces731D
题目大意:给出许多个序列,每次操作可以使这些数列中所有数+1,当一个数超过C后会还原成0,求最小需要几次操作后,满足给出的这些序列的按读入顺序且按字典序单调增,不行则输出-1。
题解:考虑每个序列和它前一个数列比较,如果能够比它前一个数列递增,能够得到一段满足的操作数的区间,然后对所有区间求交算答案即可。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } int N,C,a[2][500010],len[500010],x; struct data{int l,r;}seg[1000010]; int cnt; inline bool cmp(data A,data B) {return A.l==B.l? A.r<B.r : A.l<B.l;} int main() { N=read(),C=read(); x=0; for (int i=1; i<=N; i++,x^=1) { len[i]=read(); for (int j=1; j<=len[i]; j++) a[x][j]=read(); if (i!=1) { int le=min(len[i],len[i-1]),pos=0; for (int j=1; j<=le; j++) if (a[x][j]==a[x^1][j]) continue; else {pos=j; break;} if (!pos && len[i]<len[i-1]) {puts("-1"); return 0;} if (pos) if (a[x][pos]>a[x^1][pos]) seg[++cnt]=(data){C+1-a[x][pos],C-a[x^1][pos]}; else seg[++cnt]=(data){0,C-a[x^1][pos]},seg[++cnt]=(data){C+1-a[x][pos],C+1}; } } for (int i=1; i<=cnt; i++) if (seg[i].l>seg[i].r) swap(seg[i].l,seg[i].r); // for (int i=1; i<=cnt; i++) printf("%d %d\n",seg[i].l,seg[i].r); sort(seg+1,seg+cnt+1,cmp); int pos=0; for (int i=1; i<=cnt; i++) if (pos<seg[i].l) break; else pos=max(pos,seg[i].r+1); printf("%d\n",(pos<=C)? pos:-1); return 0; }
Codeforces731E
题目大意:给出一个游戏,首先由一个数列,每次可以从左边开始选择一段数(>2个数)合并,并得到这段的分数,只剩一个数时结束,问双方都执行最优策略的时候,游戏结束时A最多比B多多少分。
题解:比较容易的dp,可以用前缀和优化,记录一下后缀最大值。dp的方程是$dp[i]=max(dp[i],sum[j]-dp[j])$,然而dp的值完全不用记录,可以直接扫一遍。
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> using namespace std; int N,a[200010],s[200010],ans; int main() { scanf("%d",&N); for (int i=1; i<=N; i++) scanf("%d",&a[i]),s[i]=s[i-1]+a[i]; ans=s[N]; for (int i=N-1; i>1; i--) ans=max(ans,s[i]-ans); printf("%d\n",ans); return 0; }
Codeforces731F
题目大意:给一个序列,问从序列中找一个数,把不小于它的数变成它或者它的倍数,并使得这些数的和最大
题解:可以直接处理前缀和后暴力更新答案,之所以暴力的复杂度非常科学,因为这样是$\sum \frac{n}{i}$是调和级数,而它是发散的,所以复杂度并不大。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; #define LL long long int N,a[1000010],n[1000010],sn[1000010],maxx; LL ans,sum; int main() { scanf("%d",&N); for (int i=1; i<=N; i++) scanf("%d",&a[i]),n[a[i]]++,maxx=max(maxx,a[i]); for (int i=maxx; i; i--) sn[i]=sn[i+1]+n[i]; for (int i=1,j=i; i<=maxx; i++,j=i) for (ans=max(ans,sum),sum=0; n[i] && j<=maxx; j+=i) sum+=(LL)j*(sn[j]-sn[j+i]); ans=max(ans,sum); printf("%I64d\n",ans); return 0; }
Codeforces148E
题目大意:有N行数,每次可以从某行的开头或结尾去掉一个数,并得到这个数的价值,问最多取M次的最大价值和是多少。
题解:背包DP,先一遍DP预处理出$f[i][j]$表示第i行取j个数的最大价值和,再利用$f[i][j]$进行DP求出$g[i][j]$表示前i行取j个数的最大价值和。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define size(i) s[i].size()-1 vector<int>s[110],sum[110]; int f[110][10010],g[110][10010],N,M; int main() { for (int i=1; i<=100; i++) s[i].push_back(0),sum[i].push_back(0); N=read(),M=read(); for (int x,i=1,len,j; i<=N; i++) for (len=read(),j=1; j<=len; j++) x=read(),s[i].push_back(x),sum[i].push_back(sum[i][j-1]+s[i][j]); for (int i=1; i<=N; i++) for (int j=1; j<=size(i); j++) for (int k=1; k+size(i)-j-1<=size(i); k++) f[i][j]=max(f[i][j],sum[i][size(i)]-(sum[i][k+size(i)-j-1]-sum[i][k-1])); // for (int i=1; i<=N; i++,puts("")) // for (int j=1; j<=size(i); j++) // printf("%d ",f[i][j]); for (int i=1,j,len=0; i<=N; i++) for (len+=size(i),j=1; j<=len; j++) for (int k=0; k<=size(i); k++) if (j>=k && len-(size(i))>=j-k) g[i][j]=max(g[i][j],g[i-1][j-k]+f[i][k]); // for (int i=1; i<=N; i++,puts("")) // for (int j=1; j<=size(i); j++) // printf("%d ",g[i][j]); printf("%d\n",g[N][M]); return 0; }
Codeforces285E
题目大意:求出满足good position的个数恰好为K个的全排列的个数 good position定义是$|a_{i}-i|=1$
题解:这类题显然可以DP+容斥。$dp[i][j][k][l]$表示前i个已经放了j个gp,k表示i是否被放过,l表示i+1是否被放过,这样就可以转移。 最后统计答案,注意$ans[j]$出现在$ans[i]$中的次数是$C[j][i]$次。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; #define LL long long inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } int N,K; LL ans[1010],f[1010][1010][2][2],fac[1010],C[1010][1010]; #define P 1000000007 int main() { fac[0]=1; for (int i=1; i<=1000; i++) fac[i]=fac[i-1]*i%P; N=read(),K=read(); C[0][0]=1; for (int j,i=1; i<=N; i++) for (C[i][0]=1,j=1; j<=i; j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%P; f[0][0][1][0]=1; for (int i=1; i<=N; i++) for (int j=0; j<=N-1; j++) for (int k=0; k<=1; k++) for (int l=0; l<=1; l++) (f[i][j+1][l][0]+=(!k)? f[i-1][j][k][l]:0)%=P, (f[i][j+1][l][1]+=(i!=N)? f[i-1][j][k][l]:0)%=P, (f[i][j][l][0]+=f[i-1][j][k][l])%=P; for (int i=0; i<=N; i++) for (int j=0; j<=1; j++) for (int k=0; k<=1; k++) (ans[i]+=f[N][i][j][k])%=P; for (int i=0; i<=N; i++) (ans[i]*=fac[N-i])%=P; for (int i=N; i>=0; i--) for (int j=i+1; j<=N; j++) ans[i]=(ans[i]-ans[j]*C[j][i])%P,ans[i]+=(ans[i]<0)? P:0; printf("%I64d\n",ans[K]); return 0; }
Codeforces148D
题目大意:一个袋子中一共有w个白球,b个黑球,公主和龙依次取一个球,取到率先白球的人获胜,每次龙取球,总会额外随机掉出一个球,求公主获胜的概率。
题解:显然概率DP,而且很好转移,$dp[i][j]$表示现在还剩i个白球,j个黑球的时公主先手的获胜概率,那么分情况讨论即可,转移可以看代码。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; double f[1010][1010]; int W,B; int main() { for (int i=1; i<=1000; i++) f[i][0]=1,f[0][i]=0; for (int i=1; i<=1000; i++) for (int j=1; j<=1000; j++) f[i][j]+=(double)i/(i+j), f[i][j]+=(j>=3)? ((double)j/(i+j))*((double)(j-1)/(i+j-1))*((double)(j-2)/(i+j-2))*((double)f[i][j-3]) : 0.0, f[i][j]+=(j>=2 && i>=1)? ((double)j/(i+j))*((double)(j-1)/(i+j-1))*((double)i/(i-1+j-1))*((double)f[i-1][j-2]) : 0.0; while (scanf("%d%d",&W,&B)!=EOF) printf("%.9lf\n",f[W][B]); return 0; }
Codeforces268D
题目大意:一共有N层,每层有4个方向可以爬,一次最多爬高度H,从底部爬到至少N-H+1~N的高度的方案数。
题解:自己只会$O(NH^4)$的方法,就是$dp[i][n][e][s][w]$表示当前在第i层,北东南西四个方向分别距离为n/e/s/w的方案数,这样还需要滚动掉第i维。
正解也是如此,不过有很巧妙的优化,因为每一层的操作总会存在一个距离为0的转移,所以就可以压掉一维状态,这样复杂度就是$O(NH^{3})$,这种优化或者可以理解为把这些状态旋转的记录下来,可以类比旋梯。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; #define LL long long #define P 1000000009 int N,H; LL f[2][31][31][31][31],ans; #define dpn(now,nor,eas,sou,wes) f[now&1][nor<H? 0:H][eas+(eas<H)][sou+(sou<H)][wes+(wes<H)] #define dpe(now,nor,eas,sou,wes) f[now&1][nor+(nor<H)][eas<H? 0:H][sou+(sou<H)][wes+(wes<H)] #define dps(now,nor,eas,sou,wes) f[now&1][nor+(nor<H)][eas+(eas<H)][sou<H? 0:H][wes+(wes<H)] #define dpw(now,nor,eas,sou,wes) f[now&1][nor+(nor<H)][eas+(eas<H)][sou+(sou<H)][wes<H? 0:H] int main() { scanf("%d%d",&N,&H); f[0][0][0][0][0]=1; for (int i=1; i<=N; i++,memset(f[i&1],0,sizeof(f[i&1]))) for (int nor=0; nor<=H; nor++) for (int eas=0; eas<=H; eas++) for (int sou=0; sou<=H; sou++) for (int wes=0; wes<=H; wes++) (dpn(i,nor,eas,sou,wes)+=f[(i-1)&1][nor][eas][sou][wes])%=P, (dpe(i,nor,eas,sou,wes)+=f[(i-1)&1][nor][eas][sou][wes])%=P, (dps(i,nor,eas,sou,wes)+=f[(i-1)&1][nor][eas][sou][wes])%=P, (dpw(i,nor,eas,sou,wes)+=f[(i-1)&1][nor][eas][sou][wes])%=P; for (int nor=0; nor<=H; nor++) for (int eas=0; eas<=H; eas++) for (int sou=0; sou<=H; sou++) for (int wes=0; wes<=H; wes++) (ans+=f[N&1][nor][eas][sou][wes])%=P; ans=(ans-f[N&1][H][H][H][H]+P)%P; printf("%I64d\n",ans); return 0; }
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; #define LL long long #define P 1000000009 int N,H; LL f[2][2][31][31][31],ans; int main() { scanf("%d%d",&N,&H); f[0][0][0][0][0]=1; for (int i=1; i<=N; i++,memset(f[i&1],0,sizeof(f[i&1]))) for (int a=0; a<=1; a++) for (int b=0; b<=H; b++) for (int c=0; c<=H; c++) for (int d=0; d<=H; d++) (f[i&1][!(a<1)][b+(b<H)][c+(c<H)][d+(d<H)]+=f[(i-1)&1][a][b][c][d])%=P, (f[i&1][!(b<H)][c+(c<H)][d+(d<H)][a<1? 1:H]+=f[(i-1)&1][a][b][c][d])%=P, (f[i&1][!(c<H)][d+(d<H)][a<1? 1:H][b+(b<H)]+=f[(i-1)&1][a][b][c][d])%=P, (f[i&1][!(d<H)][a<1? 1:H][b+(b<H)][c+(c<H)]+=f[(i-1)&1][a][b][c][d])%=P; for (int a=0; a<=1; a++) for (int b=0; b<=H; b++) for (int c=0; c<=H; c++) for (int d=0; d<=H; d++) (ans+=f[N&1][a][b][c][d])%=P; ans=(ans-f[N&1][1][H][H][H]+P)%P; printf("%I64d\n",ans); return 0; }
Codeforces697D
题目大意:模拟求树的DFS序的过程,要求求出每个节点的DFS序的期望,以1号为根。
题解:树形DP,一个节点的DFS序编号为它前面的被遍历到的点的数目,所以可以考虑统计出每个节点的子树size,然后可以得到dp方程$f[v]=f[u]+1+\frac{size[u]-size[v]-1}{2}$,然后就可以得到了。这种题必须要手画数据才能得到!!
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define MAXN 100010 int N; struct EdgeNode{int next,to;}edge[MAXN<<1]; int head[MAXN],cnt=1,size[MAXN]; inline void AddEdge(int u,int v) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v;} inline void InsertEdge(int u,int v) {AddEdge(u,v); AddEdge(v,u);} double f[MAXN]; void DFS_1(int now,int last) { size[now]=1; for (int i=head[now]; i; i=edge[i].next) if (edge[i].to!=last) DFS_1(edge[i].to,now),size[now]+=size[edge[i].to]; } void DFS_2(int now,int last) { for (int i=head[now]; i; i=edge[i].next) if (edge[i].to!=last) f[edge[i].to]=f[now]+1+(double)(size[now]-size[edge[i].to]-1)/2,DFS_2(edge[i].to,now); } int main() { N=read(); for (int x,i=2; i<=N; i++) x=read(),InsertEdge(x,i); DFS_1(1,0); f[1]=1; DFS_2(1,0); for (int i=1; i<=N; i++) printf("%.1lf ",f[i]); return 0; }