欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【题解】[NOI Online #2 提高组] 游戏

程序员文章站 2024-03-17 16:30:52
...

LG6478 [NOI Online #2\rm LG6478\ [NOI\ Online\ {\tt \#}2 提高组]] 跑步

Description\rm Description

A\rm A 和小 B\rm B 正在玩一个游戏:有一棵包含 n=2mn=2m 个点的有根树(点从 1n1\sim n 编号),它的根是 11 号点,初始时两人各拥有 mm 个点。游戏的每个回合两人都需要选出一个自己拥有且之前未被选过的点,若对手的点在自己的点的子树内,则该回合自己获胜;若自己的点在对方的点的子树内,该回合自己失败;其他情况视为平局。游戏共进行 mm 回合。

作为旁观者的你只想知道,在他们随机选点的情况下,第一次非平局回合出现时的回合数的期望值。

为了计算这个期望,你决定对于 k=0,1,2,,mk=0,1,2,\cdots,m,计算出非平局回合数为 kk 的情况数。两种情况不同当且仅当存在一个小 A\rm A 拥有的点 xx,小 B\rm Bxx 被小 A\rm A 选择的那个回合所选择的点不同。

由于情况总数可能很大,你只需要输出答案对 998244353998244353 取模后的结果。

Solution\rm Solution

fi,jf_{i,j} 表示 ii 子树内,非平局对为 jj 对的方案数,有状态转移方程

fi,j=k1+k2++kn=jfson1,k1×fson2,k2××fsonn,knf_{i,j}=\sum_{k_1+k_2+\cdots+k_n=j}f_{{\rm son_1},k_1} \times f_{{\rm son_2},k_2} \times \cdots \times f_{{\rm son}_n,k_n}

直接枚举显然不行,考虑每遍历一棵子树就把答案并进去,则有

fi,j+k=vsonifv,j×fi,kf_{i,j+k}=\sum_{v \in son_i}f_{v,j} \times f_{i,k}

这个式子看起来是 O(n3){\rm O}(n^3) 的,但是如果卡 j,kj,k 的上界就会变成 O(n2){\rm O}(n^2),证明为每个点 (x,y)(x,y) 只会对 LCAx,y{\rm LCA}_{x,y} 有一个复杂度的贡献。

最后的时候再把 ii 个贡献加进去,分别设 Ai,Bi{\rm A}_i,{\rm B}_iii 子树内小 A/B\rm A/B 的节点个数,假设当前节点是小 A\rm A 的节点,那么就有
fi,j=fi,j+fi,j+1×max(Bij+1,0)f_{i,j}=f_{i,j}+f_{i,j+1} \times \max\big({\rm B}_i-j+1,0\big)

即之前有 j1j-1 个非平局对,那么就会消耗掉 j1j-1 个小 B\rm B 的节点,所以现在还剩 Bij+1{\rm B}_i-j+1 个小 B\rm B 的节点可用。类似的,如果当前的节点是小 B\rm B 的节点就把上文的 Bi{\rm B}_i 换成 Ai{\rm A}_i 即可。

至此我们就可以在 O(n2){\rm O}(n^2) 的时间复杂度内算出所有子树内选 kk 个非平局对的方案数。

我们先设 gk=f1,k×(n2k)!g_k=f_{1,k} \times \bigg(\dfrac{n}{2}-k\bigg)!,也就是说先固定选出来的 kk 个非平局对,然后把剩下的*组合。

但是这样会算重,因为*组合也可能贡献非平局对。我们设 fif_i 表示恰好有 ii 个非平局对的方案数,可以发现对于每个 fif_i,它对 gkg_k 的贡献是 (ik)×fi\dbinom{i}{k} \times f_i,那么我们就有 gk=i=km(ik)×fig_k=\sum\limits_{i=k}^m \dbinom{i}{k} \times f_i

这个可以二项式反演一下,有公式

fn=i=nm(in)gi    gn=i=nm(1)in(in)fif_n=\sum^m_{i=n}\dbinom{i}{n} g_i \iff g_n=\sum^m_{i=n}(-1)^{i-n} \dbinom{i}{n} f_i

套进去便得到了 fif_i

Code\rm Code

#define int long long
#define mod 998244353
int n,x,y,num_edge,fac[5001],c[5001][5001],head[5001],f[5001][5001],g[5001],siz[5001],s[5001];
char str[5001];
struct EDGE {
	int v,next;
}E[10001];
void ADD_EDGE(int u,int v) {
	E[++num_edge].v=v;
	E[num_edge].next=head[u];
	head[u]=num_edge;
}
void DFS(int u,int fath) {
	siz[u]=1;
	s[u]=str[u]-'0';
	f[u][0]=1;
	for (int i=head[u];i;i=E[i].next)
		if (E[i].v!=fath) {
			DFS(E[i].v,u);
			for (int j=0;j<=siz[u]+siz[E[i].v];j++) g[j]=0;
			for (int j=0;j<=min(siz[u],n/2);j++)
				for (int k=0;k<=min(siz[E[i].v],n/2-j);k++)
					(g[j+k]+=f[u][j]*f[E[i].v][k]%mod)%=mod;
			for (int j=0;j<=siz[u]+siz[E[i].v];j++) f[u][j]=g[j];
			siz[u]+=siz[E[i].v];
			s[u]+=s[E[i].v];
		}
	for (int i=min(s[u],siz[u]-s[u]);i>=1;i--)
		if (str[u]=='1') (f[u][i]+=f[u][i-1]*(siz[u]-s[u]-i+1))%=mod;
		else (f[u][i]+=f[u][i-1]*(s[u]-i+1))%=mod;
}
signed main() {
	scanf("%lld%s",&n,str+1);
	fac[0]=1;
	for (int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
	c[0][0]=1;
	for (int i=1;i<=n;i++) c[i][0]=1;
	for (int i=1;i<=n;i++)
		for (int j=1;j<=i;j++)
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
	for (int i=1;i<n;i++) {
		scanf("%d%d",&x,&y);
		ADD_EDGE(x,y);
		ADD_EDGE(y,x);
	}
	DFS(1,0);
	for (int i=0;i<=n/2;i++) (f[1][i]*=fac[n/2-i])%=mod;
	for (int i=0;i<=n/2;i++) {
		int ans=0;
		for (int j=i;j<=n/2;j++)
			if ((j-i)&1) ((ans-=c[j][i]*f[1][j]%mod)+=mod)%=mod;
			else (ans+=c[j][i]*f[1][j]%mod)%=mod;
		printf("%lld\n",ans);
	}
	return 0;
}