Codeforces Round #402 (Div. 1)
A题卡壳了,往离线倒着加那方面想了会儿,后来才发现方向错了,二十多分钟才过掉,过了B后做D,想法好像有点问题,最后只过两题,掉分了,差一点回紫。
AC:AB Rank:173 Rating:2227-23->2204
A.String Game
题目大意:给出字符串A和B,保证B是A的子序列,给出一个长度为A串长度的排列,问按排列的顺序删掉A串中字符,最多删几个使得B任然是A的子序列。(A串长度<=200,000)
思路:发现答案满足单调性,二分删几个,O(n)check一下B还是不是A的子序列,复杂度O(nlogn)。
#include<cstdio> inline int read() { int x;char c; while((c=getchar())<'0'||c>'9'); for(x=c-'0';(c=getchar())>='0'&&c<='9';)x=(x<<3)+(x<<1)+c-'0'; return x; } #define MN 200000 char a[MN+5],b[MN+5],s[MN+5]; int p[MN+5]; bool check(int x) { int i,j; for(i=0;a[i];++i)s[i]=a[i]; for(i=0;i<x;++i)s[p[i]-1]=' '; for(i=j=0;s[i]&&b[j];++i)if(s[i]==b[j])++j; return !b[j]; } int main() { int i,l=0,r,mid,ans; scanf("%s%s",a,b); for(r=0;a[r];++r)p[r]=read(); while(l<=r) { mid=l+r>>1; if(check(mid))ans=mid,l=mid+1; else r=mid-1; } printf("%d",ans); }
B.Bitwise Formula
题目大意:给出n条语句和语句中数字的二进制串长m,每条语句会新定义一个变量,并给它赋值,赋值语句有两种,一种给出一个长为m的二进制常量,另一种给出两个出现过的变量或"?",以及一个位运算符(AND、OR、XOR)。所有"?"可以用同一个长为m的二进制串代替,问这个串最小为多少时,所有变量的和最大/最小。(N<=5000,M<=1000)
思路:二进制的每一位独立,分别枚举取0取1就可以了,复杂度O(nm)。
#include<cstdio> #include<iostream> #include<algorithm> #include<map> using namespace std; inline int read() { int x;char c; while((c=getchar())<'0'||c>'9'); for(x=c-'0';(c=getchar())>='0'&&c<='9';)x=(x<<3)+(x<<1)+c-'0'; return x; } #define MN 5000 #define MM 1000 map<string,int> mp; int n,a[MN+5][MM+5],cnt; int t[MN+5],ta[MN+5],tb[MN+5]; int aa[MM+5],bb[MM+5]; int f[MN+5]; int check(int p,int k) { int i,s=0; for(i=1;i<=n;++i) { if(!t[i])f[i]=a[i][p]; if(t[i]==1)f[i]=(ta[i]?f[ta[i]]:k)&(tb[i]?f[tb[i]]:k); if(t[i]==2)f[i]=(ta[i]?f[ta[i]]:k)|(tb[i]?f[tb[i]]:k); if(t[i]==3)f[i]=(ta[i]?f[ta[i]]:k)^(tb[i]?f[tb[i]]:k); s+=f[i]; } return s; } int main() { int m,i,j,x,y;string s; n=read();m=read(); for(i=1;i<=n;++i) { cin>>s;mp[s]=++cnt;cin>>s; cin>>s; if(s[0]=='0'||s[0]=='1') { for(j=0;j<m;++j)a[i][j]=s[j]-'0'; continue; } ta[i]=s[0]=='?'?0:mp[s]; cin>>s; if(s[0]=='A')t[i]=1; if(s[0]=='O')t[i]=2; if(s[0]=='X')t[i]=3; cin>>s; tb[i]=s[0]=='?'?0:mp[s]; } for(i=0;i<m;++i) { x=check(i,0);y=check(i,1); aa[i]=y<x;bb[i]=y>x; } for(i=0;i<m;++i)printf("%d",aa[i]);puts(""); for(i=0;i<m;++i)printf("%d",bb[i]); }
C.Peterson Polyglot
题目大意:给出一棵n个节点的Trie树,可以选择一个p,删除Trie树各节点表示的字符串中第p个字符后再组成一棵新的Trie树,求新Trie树的最少节点数和此时应选的最小的p。(n<=300,000)
思路:对树上每个节点统计以该节点深度(根为1)作为p时这棵子树对答案的贡献,贡献即为以该节点各儿子为根的子树合并成一个的大小减去以这个节点为根的子树的大小。合并我们可以用可持久化思想,直接建新的点连向原树,这样两棵子树合并,最多新建相当于小的子树的大小的节点,发现复杂度与启发式合并整棵Trie树相同。总复杂度O(26nlogn)(常数较小)。
#include<cstdio> inline int read() { int x;char c; while((c=getchar())<'0'||c>'9'); for(x=c-'0';(c=getchar())>='0'&&c<='9';)x=(x<<3)+(x<<1)+c-'0'; return x; } #define MN 600000 int n,c[MN+5][26],f[MN+5],cnt,tn; int merge(int x,int y) { if(!x)return y;if(!y)return x; int r=++tn;++cnt; for(int i=0;i<26;++i)c[r][i]=merge(c[x][i],c[y][i]); return r; } void dfs(int x,int d) { int i,rt=++(tn=n);cnt=0; for(i=0;i<26;++i)if(c[x][i])rt=merge(rt,c[x][i]); f[d]+=cnt; for(i=0;i<26;++i)if(c[x][i])dfs(c[x][i],d+1); } int main() { int i,x,y; for(n=read(),i=1;i<n;++i)x=read(),y=read(),c[x][getchar()-'a']=y; dfs(1,1); for(i=1,x=0;i<=n;++i)if(f[i]>x)x=f[i],y=i; printf("%d\n%d",n-x,y); }
D.Parquet Re-laying
题目大意:给出一个n*m的棋盘,和两个覆盖满1*2棋子的状态,每次允许把相邻两个摆向相同的棋子旋转90°,要求构造出不超过100,000步的方案从一个状态转成另一个状态,无解输出-1。(n,m<=50)
思路:每次从上到下,从左到右找到能旋的横着的棋子,全部转为竖着;再每次找到竖着的棋子转为横着,可以证明最后一定后转成全为横或全为竖,把两个状态都这样做一遍再把第二个的操作反过来输出就可以了。复杂度O(能过)。
#include<cstdio> #define MN 50 #define MK 100000 char s[MN+5][MN+5]; int n,m,x[MK+5],y[MK+5],xn,cnt,t,k[256]; void solve() { int i,j; for(i=k[t]=0;i<n;++i) { scanf("%s",s[i]); for(j=0;j<m;++j)++k[s[i][j]]; } while(k[t]) { for(i=0;i<n;++i)for(j=0;j<m;++j)if(s[i][j]=='U'&&s[i][j+1]=='U') x[++cnt]=i+1,y[cnt]=j+1,s[i][j]=s[i+1][j]='L',s[i][j+1]='R',k['U']-=2,k['L']+=2; if(!k[t])return; for(i=0;i<n;++i)for(j=0;j<m;++j)if(s[i][j]=='L'&&s[i+1][j]=='L') x[++cnt]=i+1,y[cnt]=j+1,s[i][j]=s[i][j+1]='U',s[i+1][j]='D',k['U']+=2,k['L']-=2; } } int main() { scanf("%d%d",&n,&m); t=n&1?'U':'L'; solve();xn=cnt; solve();printf("%d\n",cnt); for(int i=1;i<=xn;++i)printf("%d %d\n",x[i],y[i]); for(int i=cnt;i>xn;--i)printf("%d %d\n",x[i],y[i]); }
E.Selling Numbers
题目大意:给一个由数字和"?"组成的串,你可以用0~9替换"?",组成一个数,但要求这个数首位不能为0,给你n个数,把这n个数都分别加上你选出的数,给出0~9各个数字的权值,要求最后n个数中各个出现的数字的权值和加起来最大,求出这个值。(出现的数字和串都不超过1000位,n<=1000)
思路:考虑数位DP,从末尾往前推,每一位的状态只与后一位是否进位有关,发现若把每个数字当前处理过的部分拿出来排个序,进位的数是连续的,用f[i][j]表示当前处理到第i位,每个数后i位从大到小排序后前j个有进位时的最大权值和,每次枚举当前串选哪个数字就能转移了,排序的方法可以以第i位为第1关键字,后i-1位的大小顺序为第2关键字进行计数排序,再注意下一些实现细节就可以了。总复杂度O(10nL)(L表示数和串的长度)。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define MN 1001 char a[MN+5],b[MN+5][MN+5],s[MN+5]; int l[MN+5],f[MN+5][MN+5],g[MN+5][MN+5],sa[MN+5][MN+5],c[MN+5],ss[10]; void read(char*a,int p) { scanf("%s",s); while(s[l[p]])++l[p]; for(int i=0;s[i];++i)a[l[p]-i]=s[i]; } int get(int x,int y){return b[x][y]?b[x][y]-'0':0;} int value(int x,int y){return y>max(l[x],l[0])&&!g[x][y]?0:c[g[x][y]];} int main() { int n,i,j,k,r,p,v; read(a,0);scanf("%d",&n); for(i=1;i<=n;++i)read(b[i],i); for(i=0;i<10;++i)scanf("%d",&c[i]); for(i=1;i<=n;++i)sa[0][i]=i; memset(f,128,sizeof(f));f[0][0]=0; for(i=1;i<=MN;++i) { memset(ss,0,sizeof(ss)); for(j=1;j<=n;++j)++ss[get(j,i)]; for(j=9;j;--j)ss[j-1]+=ss[j]; for(j=n;j;--j)sa[i][ss[get(sa[i-1][j],i)]--]=sa[i-1][j]; if(i>l[0])j=r=0; else if(a[i]=='?')j=i==l[0],r=9; else j=r=a[i]-'0'; for(;j<=r;++j) { for(p=v=0,k=1;k<=n;++k) { g[k][i]=get(k,i)+j; if(g[k][i]>9)g[k][i]-=10,++p; v+=value(k,i); } for(k=0;;) { f[i][p]=max(f[i][p],f[i-1][k]+v); if(k++==n)break; v-=value(sa[i-1][k],i); if(++g[sa[i-1][k]][i]>9)g[sa[i-1][k]][i]=0,++p; v+=value(sa[i-1][k],i); } } } printf("%d",f[MN][0]); }