正题
题目链接:https://www.luogu.com.cn/problem/CF838C
题目大意
一个字符串sss,两个人轮流操作,每次每个人可以选择删掉一个字符或者重排列这个字符串,但是不能出现之前出现过的字符串,不能操作者输。
求有多少个长度为nnn且字符集大小为kkk的字符串使得先手必胜。
1≤n≤250000,1≤k≤261\leq n\leq 250000,1\leq k\leq 261≤n≤250000,1≤k≤26
解题思路
显然如果删掉一个字符能使得先手必败,那么先手必胜。如果不能,那么肯定会一直重排列这个字符串。
那么如果一个字符串的排列数是偶数,那么先手可以切换先后手,所以先手必胜。否则先手需要考虑能否删除一个字符使得先手必败。
设第iii个字符的数量是aia_iai,那么一个字符串可重排列的方案就是n!∏i=1kai!\frac{n!}{\prod_{i=1}^ka_i!}∏i=1kai!n!,并且如果删除一个字符iii,那么排列方式将会乘上ain\frac{a_i}{n}nai。
那么如果nnn是奇数,肯定存在一个aia_iai是奇数,也就是说删除一个iii后排列方式的奇偶性不变。所以如果一个nnn是奇数且字符串的排列数是奇数,那么肯定可以删除一个字符使得排列数仍然是偶数。
那么如果nnn是奇数且先手,那么肯定不会被逼到一个走动后先手必胜的位置,所以nnn是奇数先手必胜。
然后如果nnn是偶数,那么如果排列方式是偶数那么先手必胜否则先手必败。
然后考虑怎么计数nnn是偶数的情况。考虑到一个n!n!n!包含的222质因数的个数为∑i=0⌊n2i⌋\sum_{i=0}\lfloor\frac{n}{2^i}\rfloor∑i=0⌊2in⌋,那么如果一个字符串的排列方式是偶数那么肯定有
∑i=0⌊n2i⌋=∑p=1k∑i=0⌊ap2i⌋\sum_{i=0}\left\lfloor\frac{n}{2^i}\right\rfloor=\sum_{p=1}^k\sum_{i=0}\left\lfloor\frac{a_p}{2^i}\right\rfloori=0∑⌊2in⌋=p=1∑ki=0∑⌊2iap⌋
然后又因为假设我们考虑一直分解xxx出来,显然∏i=1kai!\prod_{i=1}^ka_i!∏i=1kai!的xxx肯定不会比n!n!n!中xxx多,同理分解2k2^k2k出来也是一样的,所以它们每一个iii求出来的答案都是恰好相等的,即
∀i∈N,⌊n2i⌋=∑p=1k⌊ap2i⌋\forall i\in N,\left\lfloor\frac{n}{2^i}\right\rfloor=\sum_{p=1}^k\left\lfloor\frac{a_p}{2^i}\right\rfloor∀i∈N,⌊2in⌋=p=1∑k⌊2iap⌋
那么aia_iai求和的时候二进制就不能有进位了,也就是说对于nnn中的每个111,aia_iai都恰好有一个是111,nnn中的每一个000,aia_iai这一位都是000。
也就是把nnn的二进制分成若干份aia_iai,每一份的贡献是1ai!\frac{1}{a_i!}ai!1,要求贡献的乘积和。这个分出一个部分来的转移其实就是子集卷积,所以我们跑kkk次子集卷积即可。
这样跑有点慢,所以我们还需要快速幂优化。
时间复杂度:O(klogknlog2n)O(k\log kn\log ^2n)O(klogknlog2n)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lowbit(x) (x&-x)
using namespace std;
const int N=1<<19;
int n,k,P,f[20][N],g[20][N],c[N],inv[N],fac[N];
void FWT(int *f,int n,int op){for(int p=2;p<=n;p<<=1)for(int k=0,len=p>>1;k<n;k+=p)for(int i=k;i<k+len;i++)(f[i+len]+=f[i]*op)%=P;return;
}
signed main()
{scanf("%d%d%d",&n,&k,&P);int ans=1;for(int i=1;i<=n;i++)ans=1ll*ans*k%P;fac[0]=inv[0]=inv[1]=1;for(int i=2;i<N;i++)inv[i]=P-1ll*inv[P%i]*(P/i)%P;for(int i=1;i<N;i++)fac[i]=1ll*fac[i-1]*i%P,inv[i]=1ll*inv[i-1]*inv[i]%P;if(n&1)return printf("%d\n",ans)&0;f[0][0]=1;int m=1,lg=0;while(m<=n)m<<=1,lg++;for(int i=1;i<m;i++)c[i]=c[i-lowbit(i)]+1;for(int i=0;i<=n;i++)g[c[i]][i]=inv[i];for(int i=0;i<c[n];i++)FWT(g[i],m,1);FWT(f[0],m,1);while(k){if(k&1){for(int i=c[n];i>=0;i--){for(int x=0;x<m;x++)f[i][x]=1ll*f[i][x]*g[0][x]%P;for(int j=1;j<=i;j++)for(int x=0;x<m;x++)f[i][x]=(f[i][x]+1ll*f[i-j][x]*g[j][x])%P;}}for(int i=c[n];i>=0;i--){for(int x=0;x<m;x++)g[i][x]=(i?2ll:1ll)*g[0][x]*g[i][x]%P;for(int j=1;j<i;j++)for(int x=0;x<m;x++)g[i][x]=(g[i][x]+1ll*g[j][x]*g[i-j][x])%P;}k>>=1;}FWT(f[c[n]],m,-1);ans=(ans-1ll*f[c[n]][n]*fac[n]%P)%P;printf("%d\n",(ans+P)%P);return 0;
}