AtCoder Grand Contest 026题解
前四个题都很基础啊。
E - Synchronized Subsequence
这种思路不是很好想啊。
我们发现这个题直接逐位确定非常的难啊,根本没法做。
正解是将原序列分成了若干个段,使得$a$的个数等于$b$的个数。
并且要保证是最小的划分。
之后有一个结论就是在同一段里$a$与对应$b$的前后关系是一样的。
这个可以用不存在一种更小的划分来证,即中间不存在前缀$cnt[a]==cnt[b]$。
对于$a$在$b$i前面的段,显然分成$ababab......$最优。
对于$a$在$b$后面的段,是一个完整的后缀最优。
证明的话就是我们删掉一个前缀并选择剩下的一些点后,剩下没有选的位置都是$b$,所以一定是选上最优。
第二种我用了一个$cnt$代表开头$b$的个数还有一个串$S$来表示这个字符串。
这样就可以用后缀自动机$O(1)Lcp$来优化到$O(n)$了。
#include <bits/stdc++.h> #define min(a,b) ((a)<(b)?(a):(b)) #define max(a,b) ((a)>(b)?(a):(b)) #define for1(a,b,i) for(int i=a;i<=b;++i) #define FOR2(a,b,i) for(int i=a;i>=b;--i) using namespace std; typedef long long ll; inline int read() { int f=1,sum=0; char x=getchar(); for(;(x<'0'||x>'9');x=getchar()) if(x=='-') f=-1; for(;x>='0'&&x<='9';x=getchar()) sum=sum*10+x-'0'; return f*sum; } #define N 10000 #define M 1000005 int n; char s[M]; int be[M],f[M],buc[M]; int id[M],Max[M],pos[M],nxt[M],sum[M],L[M],R[M]; inline bool check(int x,int xr,int y,int yr) { int len=min(xr-x,yr-y); for1(0,len,i) if(s[x+i]!=s[y+i]) return s[x+i]<s[y+i]; return xr-x<=yr-y; } inline bool check_Max(int x,int y) { if(f[x]<N&&f[y]>N) return 0; if(f[x]>N&&f[y]<N) return 1; if(f[x]<N) return f[x]>=f[y]; if(f[x]/N>f[y]/N||(f[x]/N==f[y]/N&&check(f[y]%N,R[y],f[x]%N,R[x]))) return 1; return 0; } int main() { //freopen("a.in","r",stdin); //freopen("mine.out","w",stdout); n=read()*2; scanf("%s",s+1); int shu=0; for1(1,n,i) { if(!shu) be[i]=++be[0]; else be[i]=be[i-1]; shu+=('a'==s[i])-('b'==s[i]); } for1(1,n,i) R[be[i]]=i; FOR2(n,1,i) L[be[i]]=i; for(int l=1,r;l<=n;l=r+1) { r=l; while (be[r+1]==be[l]) ++r; if(s[l]=='a') { //怎么都没想到这里错啊,太马虎了 int h=1,t=0; for1(l,r,i) if(s[i]=='a') buc[++t]=i; else pos[buc[h++]]=i; int now=l; while (1) { ++f[be[l]]; now=pos[now]+1; while (now<=r&&s[now]!='a') ++now; if(now>r) break; } } else { sum[l-1]=0; for1(l,r,i) sum[i]=sum[i-1]+(s[i]=='b'); int h=1,t=0; for1(l,r,i) if(s[i]=='b') buc[++t]=i; else pos[buc[h++]]=i; FOR2(r,l,i) if(s[i]=='b') nxt[i]=nxt[i+1]; else nxt[i]=i; int pre=sum[pos[l]],id=pos[l]; for1(l,r-1,i) if(s[i]=='b') { int x[2]={sum[pos[i]]-sum[i],pos[i]+1}; x[0]+=nxt[x[1]]-x[1]; x[1]=nxt[x[1]]; if(x[0]>pre||(x[0]==pre&&check(id,r,x[1],r))) pre=x[0],id=x[1]; } f[be[l]]=pre*N+id; } } //abab是累加,不能比较字典序。。 FOR2(be[0],1,i) { Max[i]=Max[i+1]; if(f[i]>N&&check_Max(i,Max[i])) Max[i]=i; } //for1(1,be[0],i) cout<<f[be[i]]<<" "; cout<<endl; for1(1,be[0],i) if(!Max[i]) Max[i]=i; int pos=0; while (1) { pos=Max[pos+1]; if(f[pos]>N) { FOR2(f[pos]/N,1,i) putchar('b'); for1(f[pos]%N,R[pos],i) putchar(s[i]); } else { for1(1,f[pos],i) putchar('a'),putchar('b'); } if(pos==be[0]) break; } puts(""); }
F - Manju Game
$O(n^3)$是显然的,然后就想不出来了。
正解分析性质就很多了。。
首先黑白染色,$10101010......$。
若$n$是偶数,有两个结论:
1>先手答案大于等于$max(B,W)$
2>后手答案大于等于$min(B,W)$
第一个好证啊。。直接选一个边界就好了,第二个直接归纳法证一下就好了。
这样$n$为偶数就可以直接输出了。
当$n$为奇数时,第一步若我们选了黑色,依然好说。
但是选白色就比较复杂了。
我们考虑因为如果我们选白色,之后一定一直都是我们先手,这样我们就得到了一个白色点的集合。
显然我们可以在某个区间内让我们选黑色,后手选白色,其他区间我们要白色,后手选黑色。
如果二分答案之后存在一个白色点的集合使得任意一个区间都满足$B-W>=mid$即可。
这个显然是正确的。因为我们选的那个区间一定是$B-W$最小的区间,如果后手不让我们选这个区间是更亏的。
#include <bits/stdc++.h> #define for1(a,b,i) for(int i=a;i<=b;++i) #define FOR2(a,b,i) for(int i=a;i>=b;--i) using namespace std; #define M 1000005 int n; int a[M]; inline bool check(int x) { int sum=0; for(int i=1;i<=n;i+=2) { if(sum>=x) sum=max(sum+a[i]-a[i-1],a[i]); else sum+=a[i]-a[i-1]; } return sum>=x; } int main () { //freopen("a.in","r",stdin); scanf("%d",&n); for1(1,n,i) scanf("%d",a+i); int sum[2]={0}; for1(1,n,i) sum[i&1]+=a[i]; if(!(n&1)) printf("%d %d\n",max(sum[0],sum[1]),min(sum[0],sum[1])); else { int l=0,r=sum[1],mid,ans; while (l<=r) { mid=l+r>>1; if(check(mid)) ans=mid,l=mid+1; else r=mid-1; } printf("%d %d\n",sum[0]+ans,sum[1]-ans); } }