NOI Online2 提高组 游戏 题解

题目传送门

题目大意: 给出一棵 2 n 2n 2n 个点的树,两个人分别拥有 n n n 个点,每一轮两个人会随机选择一个自己拥有的且还没有被选过的点,假如两个点之间一个是另一个的祖先,那么这一句就不是平局,问进行了 n n n 轮后,有多少种情况满足恰好有 k ∈ [ 1 , n ] k\in[1,n] k[1,n] 局非平局。

题解

恰好不好求,可以考虑求至少,然后就可以用二项式反演搞出恰好。

f [ i ] [ j ] f[i][j] f[i][j] 表示 i i i 的子树内至少有 j j j 次非平局的情况数,假设先不考虑平局的点,那么就很容易转移, 每次直接将儿子的方案和自己的方案累加在一起,最后考虑一下自己是非平局的情况,即在子树内找一个点和自己配对。

最后再去考虑平局的点,使每个 f [ 1 ] [ i ] f[1][i] f[1][i] 乘上 ( n − i ) ! (n-i)! (ni)! 即可,即让剩下的点随便配对。

然后套一发反演就能得到答案了。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 5010
#define mod 998244353

int n;char s[maxn];
struct edge{int y,next;}e[maxn<<1];
int first[maxn],len=0;
void buildroad(int x,int y){e[++len]=(edge){y,first[x]};first[x]=len;}
int fac[maxn],inv_fac[maxn];
int ksm(int x,int y){int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
void work()
{
	fac[0]=inv_fac[0]=1;
	for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%mod;
	inv_fac[n]=ksm(fac[n],mod-2);
	for(int i=n-1;i>=1;i--)inv_fac[i]=1ll*inv_fac[i+1]*(i+1)%mod;
}
int C(int x,int y){return 1ll*fac[x]*inv_fac[y]%mod*inv_fac[x-y]%mod;}
int f[maxn][maxn],size[maxn],one[maxn],dp[maxn];
void dfs(int x,int fa)
{
	size[x]=1;one[x]=(s[x]=='1');f[x][0]=1;
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y;if(y==fa)continue;dfs(y,x);
		memset(dp,0,(min(size[x]+size[y]-one[x]-one[y],one[x]+one[y])+1)<<2);
		for(int j=0;j<=min(size[x]-one[x],one[x]);j++)
		for(int k=0;k<=min(size[y]-one[y],one[y]);k++)
		dp[j+k]=(dp[j+k]+1ll*f[x][j]*f[y][k]%mod)%mod;
		size[x]+=size[y],one[x]+=one[y];
		for(int i=0;i<=min(size[x]-one[x],one[x]);i++)f[x][i]=dp[i];
	}
	for(int i=min(size[x]-one[x],one[x]);i>=1;i--)
	f[x][i]=(f[x][i]+1ll*f[x][i-1]*((s[x]=='1'?(size[x]-one[x]):one[x])-(i-1))%mod)%mod;
}

int main()
{
	scanf("%d %s",&n,s+1); work();
	for(int i=1,x,y;i<n;i++)scanf("%d %d",&x,&y),
	buildroad(x,y),buildroad(y,x); dfs(1,0);
	for(int i=0;i<=min(size[1],size[1]-one[1]);i++)f[1][i]=1ll*f[1][i]*fac[n/2-i]%mod;
	for(int i=0,ans;i<=n/2;i++)
	{
		ans=0;for(int j=i;j<=n/2;j++)
		ans=(ans+1ll*C(j,i)*((j-i)%2?mod-f[1][j]:f[1][j])%mod)%mod;
		printf("%d\n",ans);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值