[NOIOnline2提高组]游戏(二项式反演+树形背包)

[NOIOnline2提高组]游戏(二项式反演+树形背包).md

题面

分析

二项式反演的套路,设\(f(i)\)为非平局回合数至少为\(i\)的情况. \(g(i)\)为非平局回合数恰好为\(i\)的情况,则\(f(i)=\sum_{j=i}^m C_j^i g(j)\).(这里的"至少"指的是我们"钦定"有\(i\)局一定平局,其他不一定,所以至少为\(i\).因此对\(f(i)\)求后缀和不是\(g(i)\),会算重)我们要求的是\(g(k)\),但是\(g\)不太好求,考虑求\(f\)再反演得出\(g\).根据二项式反演定理\(g(i)=\sum_{j=i}^m C_{j}^i f(j)\)

\(f\)可以用树形背包来求。\(dp_{x,i}\)\(x\)的子树内至少\(i\)对选择的有祖先-后代关系的点(即非平局的回合).那么\(f(i)=dp_{1,i}\cdot(m-i)!\)(剩下的\(m-i\)对关系可以任意排序)

DP的时候先用模板的树形背包合并子树,然后考虑\(x\)这个点对答案的影响,有
\(dp_{x,i} \leftarrow dp_{x,i}+dp_{x,i-1}\cdots(\text{x子树内不被x的拥有者拥有的节点个数}-(i-1))\)
这是因为\(x\)可以和子树内不被\(x\)的拥有者拥有的,且还没被配对(减去\(i-1\))的节点形成非平局回合。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#define mod 998244353
#define maxn 5000
using namespace std;
typedef long long ll;
inline ll fast_pow(ll x,ll k){
	ll ans=1;
	while(k){
		if(k&1) ans=ans*x%mod;
		x=x*x%mod;
		k>>=1;
	}
	return ans;
}
inline ll inv(ll x){
	return fast_pow(x,mod-2);
}
ll fact[maxn+5],invfact[maxn+5];
void ini(int n){
	fact[0]=1;
	for(int i=1;i<=n;i++) fact[i]=fact[i-1]*i%mod;
	invfact[n]=inv(fact[n]);
	for(int i=n-1;i>=0;i--) invfact[i]=invfact[i+1]*(i+1)%mod;
}
ll C(int n,int m){
	return fact[n]*invfact[n-m]%mod*invfact[m]%mod;
} 

int m,n;
int a[maxn+5];
struct edge {
	int from;
	int to;
	int next;
} E[maxn*2+5];
int head[maxn+5];
int esz=1;
void add_edge(int u,int v) {
	esz++;
	E[esz].from=u;
	E[esz].to=v;
	E[esz].next=head[u];
	head[u]=esz;
}

int sz[maxn+5];
int cnt[maxn+5][2];
ll dp[maxn+5][maxn+5];//x的子树内,至少有i对祖先关系的方案数(有i对我们已经钦定,剩下的随便搞 
//在dp转移中,我们钦定i对关系来转移,最后再乘上(m-i)!,表示剩下的关系可以任意匹配 
//因为只是钦定,转移的时候就不需要考虑剩下的关系。而如果直接定义状态为恰好i对,转移时无法保证合法 
void dfs(int x,int fa) {
	static ll tmp[maxn+5];
	sz[x]=1;
	cnt[x][a[x]]++;
	dp[x][0]=1;
	for(int i=head[x];i;i=E[i].next){//先合并子树 
		int y=E[i].to;
		if(y!=fa){
			dfs(y,x);
			for(int j=0;j<=sz[x]+sz[y];j++) tmp[j]=0;
			for(int j=0;j<=sz[x];j++){
				for(int k=0;k<=sz[y];k++){
					tmp[j+k]=(tmp[j+k]+dp[x][j]*dp[y][k]%mod)%mod;
				}
			}
			for(int j=0;j<=sz[x]+sz[y];j++) dp[x][j]=tmp[j];
			sz[x]+=sz[y];
			cnt[x][0]+=cnt[y][0];
			cnt[x][1]+=cnt[y][1];
		}
	}
	//计算x的贡献 
	for(int i=cnt[x][a[x]^1];i>=1;i--){//对于dp[x][i],x可以和子树里还没被选的与a[x]颜色相反的点匹配 
		//于是贡献为dp[x][i-1]*(cnt[x][a[x]^1]-(i-1)) 倒序是为了防止重复更新 
		dp[x][i]+=dp[x][i-1]*(cnt[x][a[x]^1]-(i-1))%mod;
		dp[x][i]%=mod;
	}
}

ll f[maxn+5],g[maxn+5];
int main() {
	int u,v;
	scanf("%d",&n);
	m=n/2;
	ini(n);
	for(int i=1;i<=n;i++) scanf("%1d",&a[i]);
	for(int i=1;i<n;i++){
		scanf("%d %d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs(1,0);
	for(int i=0;i<=m;i++) f[i]=dp[1][i]*fact[m-i]%mod;//求出真正的dp值
	for(int i=0;i<=m;i++){
		for(int j=i;j<=m;j++){
			if((j-i)%2==1) g[i]-=C(j,i)*f[j]%mod;
			else g[i]+=C(j,i)*f[j]%mod;
			g[i]=(g[i]+mod)%mod;
		}
		printf("%lld\n",g[i]);
	} 
}
posted @ 2020-04-29 21:17  birchtree  阅读(188)  评论(0编辑  收藏  举报