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<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;
}
posted @ 2022-03-31 22:12  QuantAsk  阅读(65)  评论(0编辑  收藏  举报