[LOJ6436][PKUSC2018]神仙的游戏
description
给你一个只有01
和?
的字符串,问你是否存在一种把?
改成01
的方案使串存在一个长度为\(1-n\)的\(border\)。\(n\le5\times10^5\)
sol
这种题都不会我还是太菜了。
有一档部分分是“01
的个数不超过\(5000\)”。这个其实对正解的提示是蛮大的。
有一个显然的结论:存在一个长度为\(len\)的\(border\)当且仅当对于\(\forall i\in[1,len]\) 有 \(s[i]=s[n-len+i]\)。(感谢热心网友找出这里的一个错误)
或者这样说,把所有位置在模\(n-len\)意义下分组,同一组里的01
要全部相同。
那么,如果存在一组0
和1
他们的下标之差为\(x\),那么所有\(y|x\)的\(n-y\)都不可能成为\(border\)。
这样\(67\)分就可以写一个\(5000^2\)枚举01
对统计,然后每次扫一遍倍数\(O(n\log n)\)计算答案。
考虑正解。现在的复杂度瓶颈在于枚举01
对。
搞两个生成函数\(A(x)\)和\(B(x)\),\(A(x)=\sum_{i=0}^{n-1}[s_i==0]x^i\),\(B(x)=\sum_{i=0}^{n-1}[s_i==1]x^i\)。
我们知道,多项式卷积计算的是所有下标之和为一定值的乘积之和,也就是\(C_i=\sum_{j+k=i}A_jB_k\)。现在我们要统计的是差为定值的。
把一个多项式\(reverse\)过来不就好了么。形式化地,令\(A(x)=x^{n-1}A(\frac 1x)\),然后计算\(C(x)=A(x)B(x)\)得到的第\(i\)次项系数就是满足“1
的下标减0
的下标等于\(i-n\)”的01
对数。
复杂度\(O(n\log n)\)。
code
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 3e6+5;
const int mod = 998244353;
int n,len,a[N],b[N],rev[N],l,og[N];
char s[N];
int fastpow(int a,int b){
int res=1;
while (b) {if (b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
return res;
}
void ntt(int *P,int opt){
for (int i=0;i<len;++i) if (i<rev[i]) swap(P[i],P[rev[i]]);
for (int i=1;i<len;i<<=1){
int W=fastpow(3,(mod-1)/(i<<1));
if (opt==-1) W=fastpow(W,mod-2);
og[0]=1;
for (int j=1;j<i;++j) og[j]=1ll*og[j-1]*W%mod;
for (int p=i<<1,j=0;j<len;j+=p)
for (int k=0;k<i;++k){
int x=P[j+k],y=1ll*og[k]*P[j+k+i]%mod;
P[j+k]=(x+y)%mod;P[j+k+i]=(x-y+mod)%mod;
}
}
if (opt==-1) for (int i=0,Inv=fastpow(len,mod-2);i<len;++i) P[i]=1ll*P[i]*Inv%mod;
}
int main(){
scanf("%s",s);n=strlen(s);
for (len=1;len<=(n<<1);len<<=1) ++l;--l;
for (int i=0;i<len;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<l);
for (int i=0;i<n;++i) a[i]=s[i]=='0',b[i]=s[n-i-1]=='1';
ntt(a,1);ntt(b,1);
for (int i=0;i<len;++i) a[i]=1ll*a[i]*b[i]%mod;
ntt(a,-1);
long long ans=1ll*n*n;
for (int i=1;i<n;++i){
int fg=1;
for (int j=i;j<n;j+=i) if (a[n-j-1]|a[n+j-1]) {fg=0;break;}
if (fg) ans^=1ll*(n-i)*(n-i);
}
printf("%lld\n",ans);return 0;
}