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\),那么如果一个字符串的排列方式是偶数那么肯定有
然后又因为假设我们考虑一直分解\(x\)出来,显然\(\prod_{i=1}^ka_i!\)的\(x\)肯定不会比\(n!\)中\(x\)多,同理分解\(2^k\)出来也是一样的,所以它们每一个\(i\)求出来的答案都是恰好相等的,即
那么\(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<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#include<vector>
#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<pair<ll,ll> >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<q[x].size();i++){
ll p=min(q[x][i].first,len[x]);
ans[q[x][i].second]=f[x][p]+s[x]+d[x]*p;
}
return;
}
signed main()
{
n=read();m=read();
for(ll i=2,x;i<=n;i++)
x=read(),addl(x,i);
for(ll i=1;i<=m;i++){
ll x=read(),d=read();
q[x].push_back(mp(d,i));
}
dfs(1);f[1]=g;now=g;now+=len[1]+1;
solve(1);
for(ll i=1;i<=m;i++)
print(ans[i]),putchar('\n');
return 0;
}