题解 「EZEC-6」游戏

Forever_Pursuit

2021-02-08 13:18:28

Solution

- ### subtask 1 略 - ### subtask 2 最优轮数为 $n+m$,方案为 $ABAB\dots AB$ 或 $BABA\dots BA$,正确性显然。 - ### subtask 3 设 $N=n+m$,$M=\sum N$, 设 $f_{i,j}$ 表示第 $i$ 轮过后 Alice 能否恰好有 $j$ 个石子,需要保证 $0\le j\le N$,使得 Bob 的石子数也合法, 初始 $f_{0,n}=1$,如果对于某个 $i$ 和所有合法的 $j$ 都有 $f_{i,j}=0$,即无法进行到这一轮,最优轮数为 $i-1$,方案可以倒推得到,这个显然可以 $O(N^2)$ 转移。 总时间复杂度为 $O(M^2)$。 - ### subtask 4 使用 `bitset` 优化 subtask3 的做法,复杂度 $O(\frac{M^2}{\omega})$。 - ### subtask 5 设 $S=\lceil\sqrt{2N}\rceil$, 在数据较大时,通过 $1-S$ 的数的加减可以得到任何一个 $N$ 以内的正整数 $x$(在奇偶性满足的情况下),且运算途中值 $x$ 始终满足 $0\le x\le N$, 考虑在 $S$ 轮后获得一个状态,使得之后不断 $ABAB\dots AB$ 或 $BABA\dots BA$ 即可得到最优轮数,因为可以获得任何满足奇偶性的状态,所以若满足奇偶性,最优轮数为 $N$,否则为 $N-1$, 分类讨论并整合后可以得到,当 $abs(n-m)\equiv2\pmod4$ 时,答案为 $N-1$,否则答案为 $N$(注意结论只对大数据成立)。 若最优轮数为 $N$,最后 Alice 只可能有 $0$ 或 $N$ 个石子, 若最优轮数为 $N-1$,最后 Alice 只可能有 $0$,$1$,$N-1$ 或 $N$ 个石子, 钦定 $S$ 轮之后的方案为 $ABAB\dots AB$ 或 $BABA\dots BA$,可以根据答案轮数得到最后一轮后的状态,从而倒推出 $S$ 轮结束后 Alice 的石子数, 之后使用和 subtask4 相同的做法,但是只需要计算前 $S$ 轮的 $f$ 值即可,由于 $S$ 为 $\sqrt{N}$ 级别,单次复杂度为 $O(\frac{N\sqrt{N}}{\omega})$,总复杂度为 $O(\frac{M\sqrt{M}}{\omega})$。 对于小数据,可以直接采用 subtask4 的做法。 经实践,$N\ge18$ 时,关于最优轮数的结论成立。 注意,如果实现时 `bitset` 定长,则复杂度错误,为 $O(\frac{M\sqrt{MT}}{\omega})$,每次 $N=\frac{M}{T}$ 即可卡满,手写 `bitset` 可以达到正确的复杂度,如果不手写 `bitset` 通过调节阈值或设置多个阈值也可以通过。 - ### subtask 6 以下同样基于数据较大。 设 $S=6\lceil\sqrt{N}\rceil$,沿用 subtask5 部分做法,得到最优轮数,得到最后一轮的状态,钦定 $S$ 轮之后的方案,并倒推得到 $S$ 轮的状态,设当前状态 $S$ 轮结束后 Alice 有 $t$ 个石子。 考虑直接构造求解,设 $now$ 为当前 Alice 的石子数,为了方便,可以先用若干轮把 $now$ 减到尽量小,之后使用若干轮把 $now$ 加到尽量接近 $t$,此时需要分 $2$ 种情况讨论,一种是 $now\le t$,另一种是 $now>t$,这两种情况的奇偶性是不相同的, 因为 $\sum\limits_{i=1}^{\lceil2\sqrt{N}\rceil}i\ge2N$,所以此时 $now$ 和 $t$ 的差最多为 $\lceil2\sqrt{N}\rceil$,之后每次可以利用 $2$ 轮来使 $now$ 加 $1$ 或减 $1$,一直到 $now=t$ 为止,花费轮数最多为 $3\times\lceil2\sqrt{N}\rceil\le6\lceil\sqrt{N}\rceil$,即取 $S=6\lceil\sqrt{N}\rceil$ 一定足够,若当前轮数仍未到达 $S$,可以每次花费 $4$ 轮使 $now$ 加 $1$ 再减 $1$,若无法恰好用尽 $S$ 轮,本次构造失败,否则可以直接输出方案。 这些情况已经涵盖了所有 subtask5 对最优轮数的讨论 ~~甚至还多了一些~~,在大数据下一定可以找到方案,时间复杂度为 $O(N)$。 接下来讨论这个做法的适用数据范围,在关于最优轮数的结论成立的情况下,只要保证 $n,m$ 合法即可,倒推到 $S$ 轮时 $n,m$ 的差约为 $S$(正负不超过 $2$),不妨设 $n\le m$,只要 $n\ge S$,就不会出现不合法情况,算上 $\lceil2\sqrt{N}\rceil$ 的步长,$N\ge 2(S+\lceil2\sqrt{N}\rceil)+m-n$ 时适用,$m-n\le S+2$,即 $N\ge 3S+4\lceil\sqrt{N}\rceil+2=22\lceil\sqrt{N}\rceil+2$,$N\ge23^2=529$ 时,做法适用。 在运算过程中多次把下界往大算,所以这个界非常松,但是没必要卡得更紧了,实际的下界约为 $300$ 多,若多进行一些分类讨论,可以做到 $100$ 多。 做法不适用时,使用 subtask4 的算法即可,此处增加的运算次数最多为 $T\times529\times\lceil\frac{529}{64}\rceil=4761000$,再乘上一些常数,显然可以接受。 忽略上述常数,总复杂度为 $O(M)$。 实现时,倒推可以 $O(1)$ 解决,使用 $O(N)$ 的算法也可以通过,只是常数略大。 code: ```cpp #include<bits/stdc++.h> using namespace std; #define N 20000005 #define M 529 int read(){ int w=0,f=1; char c=' '; while(c<'0'||c>'9')c=getchar(),f=c=='-'?-1:f; while(c>='0'&&c<='9')w=w*10+c-48,c=getchar(); return w*f; } int n,m,s,flag; char ans[N]; bitset<M>f[M],base; void get(int x,int y){ if(!x)return; if(y-x>=0) if(f[x-1][y-x]){ get(x-1,y-x); ans[x]='A'; return; } if(y+x<=n+m) if(f[x-1][y+x]){ get(x-1,y+x); ans[x]='B'; return; } } void brute(){ base.reset(); for(int i=0;i<=n+m;i++) base[i]=1; f[0].reset(); f[0][n]=1; for(int i=1;i<=n+m;i++) f[i]=(f[i-1]<<i|f[i-1]>>i)&base; for(int i=n+m;i>=0;i--){ if(!f[i].any())continue; int x=0; for(int j=0;j<=n+m&&!x;j++) if(f[i][j])x=j; printf("%d\n",i); get(i,x); puts(ans+1); memset(ans+1,0,sizeof(char)*i); return; } } void add(int&x,int&y){ ans[y]='A',x+=y,y++; } void sub(int&x,int&y){ ans[y]='B',x-=y,y++; } void solve(int c,int f,int op){ if(flag)return; int now=1,x=n,t=op&1?n+m-f:f; if(op&1)t-=(n+m-c-s)/2; else t+=(n+m-c-s)/2; if((n+m-c-s)&1){ if((n+m-c-s-1+op)&1)t-=s+1; else t+=s+1; } while(x>=now)sub(x,now); if(op<=1){ while(x+now<=t)add(x,now); while(x<t)sub(x,now),add(x,now); } else{ while(x<=t)add(x,now); while(x>t)add(x,now),sub(x,now); } while(now<=s)sub(x,now),add(x,now),add(x,now),sub(x,now); if(now!=s+1)return; flag=1; for(int i=n+m-c;i>s;i--) if((n+m-c-i+op)&1)ans[i]='A'; else ans[i]='B'; printf("%d\n",n+m-c); puts(ans+1); memset(ans+1,0,sizeof(char)*(n+m-c)); } signed main(){ int T=read(); while(T--){ n=read(),m=read(),flag=0; if(n+m<M){ brute(); continue; } s=6*ceil(sqrt(n+m)); if(((n-m)%4+4)%4==2){ for(int i=0;i<4;i++) for(int k=0;k<2;k++) solve(1,k,i); } else{ for(int i=0;i<4;i++) solve(0,0,i); } } return 0; } ```