CF838C-Future Failure【dp,子集卷积】
正题
题目链接:https://www.luogu.com.cn/problem/CF838C
题目大意
一个字符串\(s\),两个人轮流操作,每次每个人可以选择删掉一个字符或者重排列这个字符串,但是不能出现之前出现过的字符串,不能操作者输。
求有多少个长度为\(n\)且字符集大小为\(k\)的字符串使得先手必胜。
\(1\leq n\leq 250000,1\leq k\leq 26\)
解题思路
显然如果删掉一个字符能使得先手必败,那么先手必胜。如果不能,那么肯定会一直重排列这个字符串。
那么如果一个字符串的排列数是偶数,那么先手可以切换先后手,所以先手必胜。否则先手需要考虑能否删除一个字符使得先手必败。
设第\(i\)个字符的数量是\(a_i\),那么一个字符串可重排列的方案就是\(\frac{n!}{\prod_{i=1}^ka_i!}\),并且如果删除一个字符\(i\),那么排列方式将会乘上\(\frac{a_i}{n}\)。
那么如果\(n\)是奇数,肯定存在一个\(a_i\)是奇数,也就是说删除一个\(i\)后排列方式的奇偶性不变。所以如果一个\(n\)是奇数且字符串的排列数是奇数,那么肯定可以删除一个字符使得排列数仍然是偶数。
那么如果\(n\)是奇数且先手,那么肯定不会被逼到一个走动后先手必胜的位置,所以\(n\)是奇数先手必胜。
然后如果\(n\)是偶数,那么如果排列方式是偶数那么先手必胜否则先手必败。
然后考虑怎么计数\(n\)是偶数的情况。考虑到一个\(n!\)包含的\(2\)质因数的个数为\(\sum_{i=0}\lfloor\frac{n}{2^i}\rfloor\),那么如果一个字符串的排列方式是偶数那么肯定有
\[\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\rfloor \]然后又因为假设我们考虑一直分解\(x\)出来,显然\(\prod_{i=1}^ka_i!\)的\(x\)肯定不会比\(n!\)中\(x\)多,同理分解\(2^k\)出来也是一样的,所以它们每一个\(i\)求出来的答案都是恰好相等的,即
\[\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 \]那么\(a_i\)求和的时候二进制就不能有进位了,也就是说对于\(n\)中的每个\(1\),\(a_i\)都恰好有一个是\(1\),\(n\)中的每一个\(0\),\(a_i\)这一位都是\(0\)。
也就是把\(n\)的二进制分成若干份\(a_i\),每一份的贡献是\(\frac{1}{a_i!}\),要求贡献的乘积和。这个分出一个部分来的转移其实就是子集卷积,所以我们跑\(k\)次子集卷积即可。
这样跑有点慢,所以我们还需要快速幂优化。
时间复杂度:\(O(k\log kn\log ^2n)\)
code
#pragma GCC optimize(2)
%:pragma GCC optimize(3)
%:pragma GCC optimize("Ofast")
%:pragma GCC optimize("inline")
#include
#include
#include
#include
#include
#define mp(x,y) make_pair(x,y)
#define ll long long
using namespace std;
const ll N=1e6+10;
struct node{
ll to,next;
}a[N];
ll n,m,tot,ls[N],dep[N],len[N],son[N];
ll d[N],s[N],g[N],*f[N],*now,ans[N];
vector >q[N];
ll read() {
ll x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
while(isdigit(c)) x=(x<<1)+(x<<3)+c-48,c=getchar();
return x*f;
}
void print(ll x){
if(x>9)print(x/10);putchar(x%10+48);return;
}
void addl(ll x,ll y){
a[++tot].to=y;
a[tot].next=ls[x];
ls[x]=tot;return;
}
void dfs(ll x){
for(ll i=ls[x];i;i=a[i].next){
ll y=a[i].to;dfs(y);
if(len[y]>=len[son[x]])son[x]=y;
}
if(son[x])len[x]=len[son[x]]+1;
return;
}
void solve(ll x){
if(son[x]){
f[son[x]]=f[x]+1;
solve(son[x]);
d[x]=d[son[x]];
s[x]=s[son[x]]-d[x];
f[x][0]-=s[x];
}
d[x]++;s[x]++;
for(ll i=ls[x];i;i=a[i].next){
ll y=a[i].to;
if(y==son[x])continue;
f[y]=now;now+=len[y]+1;solve(y);
int r=f[y][len[y]]+s[y]+d[y]*len[y];
s[x]+=r;f[x][0]-=r;
for(ll j=0;j<=len[y];j++)
f[x][j+1]+=f[y][j]+s[y]+d[y]*j-r;
}
for(ll i=0;i