Codeforces Round#402(Div.1)掉分记+题解
哎,今天第一次打div1 感觉头脑很不清醒。。。
看到第一题就蒙了,想了好久,怎么乱dp,倒过来插之类的...突然发现不就是一道sb二分吗.....sb二分看了二十分钟........
然后第二题看了一下,感觉太码农了不可做,然后就cd逛一逛。
突然觉得c可做,就做了一下,交上去wa了,发现有情况没考虑。
这下又滚回了b,结果又没写完......
gg 2040 -83 ->1957
-----------------------------------------------我似分割线啊
A.String Game
题意:有一个n个字符组成的字符串,并给定它的一个子串。调皮的小孩一个人会按照时间顺序每一秒删掉其中一个字符,求一个最迟的时间,使得给定的串仍然是剩下的字符串的子串。n<=200000
题解:别想太多,直接二分答案,On 来check一下。
复杂度nlogn
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #define ll long longh #define INF 2000000000 #define MAXN 200000 using namespace std; 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; } char s2[MAXN+5]; char s[MAXN+5]; int n,m; int a[MAXN+5]; int p[MAXN+5]; bool check(int x) { int j=1; for(int i=1;i<=n&&j<=m;i++) if(p[i]>x&&s[i]==s2[j]) ++j; if(j>m) return true; return false; } int main() { scanf("%s",s+1); scanf("%s",s2+1); n=strlen(s+1);m=strlen(s2+1); for(int i=1;i<=n;i++) { a[i]=read(); p[a[i]]=i; } int l=0,r=n,mid,ans; while(l<=r) { mid=(l+r)>>1; if(check(mid)) ans=mid,l=mid+1; else r=mid-1; } cout<<ans; return 0; }
B.Bitwise Formula
题意:给定n个变量的定义,每个变量都是m位的二进制数,你可以选择一个数,这些变量在定义的时候可能会用到之前的变量和你选择的数,最后的贡献为所有的变量大小之和。你要分别在贡献最大和最小的情况下,选择最小的数字。
n<=5000 m<=1000
题解:贪心。对每一位分别check(),枚举那一位选择1或者0,算一下这一位上的贡献,确定取值即可。复杂度nm
我的代码丑
#include<iostream> #include<cstdio> #include<algorithm> #include<queue> #include<cstring> #include<map> #define ID 20170226 #define ll long long 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; } string st,st2,name; map<string,int> mp; char s[10000]; int mark[5005][3]; int x[5005][1005],x2[5005][1005]; int n,m,ans[5005]; char op[5005]; int f[5005]; int check(int j,int def) { int x1,y1,tot=0; for(int i=1;i<=n;i++) { if(mark[i][1]==ID) x1=x[i][j]; else if(mark[i][1]==-1) x1=def; else x1=f[mark[i][1]]; if(mark[i][2]==ID)y1=x2[i][j]; else if(mark[i][2]==-1) y1=def; else y1=f[mark[i][2]]; // cout<<i<<" "<<j<<" "<<x1<<" "<<y1<<endl; if(op[i]=='O') x1=x1|y1; if(op[i]=='A') x1=x1&y1; if(op[i]=='X') x1=x1^y1; f[i]=x1;tot+=x1; } return tot; } int main() { n=read();m=read(); for(int i=1;i<=n;i++) { int nown=0; cin>>name;mp[name]=i; scanf("%s",s);cin>>st; if(getchar()!='\n') { scanf("%s",s); cin>>st2; if(st2=="?") mark[i][2]=-1; else if(st2[0]>='0'&&st2[0]<='9') { mark[i][2]=ID;for(int j=0;j<m;j++) x2[i][j]=st2[j]-'0';} else mark[i][2]=mp[st2]; op[i]=s[0]; } else mark[i][2]=ID,op[i]='O'; if(st=="?") mark[i][1]=-1; else if(st[0]>='0'&&st[0]<='9') { mark[i][1]=ID;for(int j=0;j<m;j++) x[i][j]=st[j]-'0';} else mark[i][1]=mp[st]; } for(int i=0;i<m;i++) { int x=check(i,0);int y=check(i,1); if(x>y) printf("1"); else if(x==y) printf("0"); else printf("0"),ans[i]=1; } puts(""); for(int i=0;i<m;i++)printf("%d",ans[i]); return 0; }
C.给定一棵trie树,你可以删掉其中一个深度的点,求重建的trie树最少有多少个点以及最少时删掉哪一个深度。
题解:如图:
我们可以发现,删掉一个深度之后减少的点的数量不仅仅是这个深度的节点的数量,还包括这个 深度的爸爸相同(1)的点 (2,3) 的相同字母(c) 的边指向的点(5,4)
这个例子中4和5可以合成一个点,实际上减少的部分还包括了4和5的相同字母的边指向的点,以此类推。
所以我们可以考虑在dfs的时候直接暴力算每个点的可合并孙子的个数,并且加入对应深度的答案当中,然后递归下去做。
具体实现见代码(向cf的一些dalao学习的)
复杂度是ditoly帮我证明的,这样做的复杂度,因为是两两合并,应该会严格小于启发式合并的复杂度,所以复杂度大概是26*n*logn
#include<iostream> #include<cstdio> #include<algorithm> #include<queue> #include<cstring> #include<map> #define MAXN 600000 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; int c[MAXN+5][26]; char ch; int sum[MAXN+5]; int newnode(int x) { for(int i=0;i<26;i++) c[m][i]=c[x][i]; return m++; } void merge(int&x,int y,int dep) { if(!x||!y){x+=y;return;} x=newnode(x);++sum[dep]; for(int i=0;i<26;i++)merge(c[x][i],c[y][i],dep); } void dfs(int x,int dep) { sum[dep-1]++;int nown=0;m=n+1; for(int i=0;i<26;i++,nown=0) for(int j=0;j<26;j++) if(c[c[x][j]][i]) merge(nown,c[c[x][j]][i],dep); for(int i=0;i<26;i++) if(c[x][i]) dfs(c[x][i],dep+1); } int main() { n=read(); for(int i=1;i<n;i++) { int u=read(),v=read();scanf("%c",&ch); c[u][ch-'a']=v; } dfs(1,1);int ans=1; for(int i=1;i<=n;i++) if(sum[i]>sum[ans]) ans=i; printf("%d\n%d",n-sum[ans],ans); return 0; }
D.Parquet Re-laying
有两个n*m并由1*2的小矩形构成的图,每次可以把两个构成2*2的小矩形的块旋转一下(横的变成竖的,竖的变成横的)
要从第一个图变到第二个图,求一种方案。n,m<=50
很显然,不管图是什么样,都能转到全是横的 或者全是竖的 的情况,所以这道题其实没有无解...
所以把两个图都转成全是横的或者全是竖的情况,倒着输出第二个就没了.....
#include<iostream> #include<cstdio> using namespace std; 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; } char s[55][55],s2[55][55],ch; int n,m,cnt=0; struct ANS{ int x,y; }A[100005]; bool check(char a[55][55]) { for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(a[i][j]==ch) return 0; return 1; } void solve(char a[55][55]) { while(1) { bool ok=1; for(;ok;) { ok=0; for(int i=1;i<n;i++) for(int j=1;j<m;j++) if(a[i][j]=='L'&&a[i+1][j]=='L') { ok=1;A[++cnt]=(ANS){i,j}; a[i][j]='U';a[i+1][j]='D'; a[i][j+1]='U';a[i+1][j+1]='D'; } } if(check(a)) break; for(ok=1;ok;) { ok=0; for(int i=1;i<n;i++) for(int j=1;j<m;j++) if(a[i][j]=='U'&&a[i][j+1]=='U') { ok=1;A[++cnt]=(ANS){i,j}; a[i][j]='L';a[i+1][j]='L'; a[i][j+1]='R';a[i+1][j+1]='R'; } } if(check(a)) break; } } int main() { n=read();m=read();ch=n&1?'U':'L'; for(int i=1;i<=n;i++)scanf("%s",s[i]+1); for(int i=1;i<=n;i++)scanf("%s",s2[i]+1); solve(s);int pre=cnt; solve(s2); cout<<cnt<<endl; for(int i=1;i<=pre;i++) printf("%d %d\n",A[i].x,A[i].y); for(int i=cnt;i>pre;i--) printf("%d %d\n",A[i].x,A[i].y); return 0; }
E.Selling Numbers
做题背景:今天(第二天,周一)下午和ditoly大佬切完了前四题之后准备看一看第五题,看了一下,发现好像比cd都可做?好像是个数位dp,但是不知道怎么做。
然后就去standing里面,发现只有30个左右的人过了,就开始一个个翻代码(没几个能看的),能看也看的懵逼,就得出了一个结论:它们都在排序。
然后就懵懵地去上体育课,想着想着,发现排序后进位连续,这样就可以表示状态了,于是就没了。
题目大意:给定n个最多1000位的十进制数,你可以选择一个数(有些位数是确定的),并把剩余的数都加上它,求贡献最大值。
贡献计算:每个数字都有一定的贡献值,每个数的贡献是它的所有数字的贡献值之和。
n<=1000
题解:很显然我们能够得到几个结论:
每一位分开计算->如果不考虑进位就是一道贪心->进位的处理是题目难点
怎么处理进位呢?我们发现 对于一个数的任意两位,假设是ab 和cd
如果a>c 那么a>=c+1,如果第二个数进位了,第一个也会进位
如果a==c,那么b>=d时也有同样的结论
所以我们可以把每一位都分别以这一位的数为第一关键字,后缀为第二关键字做一遍计数排序,
这样就能保证进位的部分肯定是连续的一段。
那么我们就可以用数位dp来计算结果了。
用f[i][j]表示后i位数字,第i位的前j个有进位的时候的最大答案。
先枚举数位,再枚举这一位选的数字,按照上一位的大小顺序,让它们一个个进位,同步更新答案。
f[i][j]=max(f[i-1][k]+合并的答案)并且第i位的数字加上上一位的k个进位,再加上这一位的选择的数字有j个进位
复杂度 10*n^2 (10*1000*1000)
然后代码丑
#include<iostream> #include<cstdio> #include<cstring> #define INF 2000000000 using namespace std; 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 a[15]; int n,m,len2; int len[1005]; int b[1005]; char s[1005],s2[1005]; int x[1005][1005]; int f[1005][1005]; int sa[1005][1005]; int rk[1005][1005]; int v[1002]; void sort() { for(int i=1;i<=n;i++) rk[0][i]=INF; for(int i=1;i<=n;i++) sa[0][i]=i; for(int i=1;i<=m;i++) { memset(v,0,sizeof(v)); for(int j=1;j<=n;j++) v[x[j][i]]++; for(int j=9;j>=0;j--) v[j]+=v[j+1]; for(int j=n;j;j--) rk[i][sa[i-1][j]]=v[x[sa[i-1][j]][i]]--; for(int j=1;j<=n;j++) sa[i][rk[i][j]]=j; } } int work(int k,int ad,int&tot) { int en=n,fn=0,i; for(register int ii=1;ii<=n;ii++) { i=sa[k][ii];b[i]=0; int xx=x[i][k]+ad; if(en==n&&xx<10) en=ii-1; if(k>len2&&len[i]<k&&xx==0) continue; fn+=a[xx%10];tot+=(xx>=10);b[i]=xx; } if(f[k-1][0]>=0) f[k][en]=max(f[k][en],f[k-1][0]+fn); return fn; } void ins(int i,int j,int ad,int&num,int&into) { int xx=x[j][i]+ad; num=num-a[xx%10]+a[(xx+1)%10]; if(i>len2&&i>len[j]&&b[j]==0) num+=a[xx%10]; into+=(xx==9); } int main() { m=1000; scanf("%s",s+1);len2=strlen(s+1); for(int i=len2,j=1;i;i--,j++) s2[j]=s[i]; for(int i=len2+1;i<=m;i++) s2[i]='0'; n=read(); for(int i=1;i<=n;i++) { scanf("%s",s+1);len[i]=strlen(s+1); for(int j=len[i],k=1;j;j--,k++) x[i][k]=s[j]-'0'; } for(int i=0;i<10;i++) a[i]=read(); sort(); for(int i=0;i<=m;i++) for(int j=0;j<=n;j++) f[i][j]=-INF; f[0][0]=0; for(register int i=1;i<=m;i++) { if(s2[i]=='?') { for(register int th=0;th<10;th++) { if(th==0&&i==len2) continue; int into=0; int num=work(i,th,into); for(register int j=1;j<=n;j++) { ins(i,sa[i-1][j],th,num,into); if(f[i-1][j]>=0) { f[i][into]=max(f[i][into],f[i-1][j]+num); } } } } else { int th=s2[i]-'0'; int into=0; int num=work(i,th,into); for(register int j=1;j<=n;j++) { ins(i,sa[i-1][j],th,num,into); if(f[i-1][j]>=0) f[i][into]=max(f[i][into],f[i-1][j]+num); } } } int ans=0; for(register int i=0;i<=n;i++) ans=max(ans,f[m][i]+i*a[1]); printf("%d",ans); return 0; }