OI题解 - 游戏match (NOI Online Round2) | Lucky_Glass's Blog
0%

OI题解 - 游戏match (NOI Online Round2)


WA?


# 题面

有一棵 $n=2m$ 个点的树,树上每个节点要么是黑点要么是白点,且各有 $m$ 个。小 A 和小 B 在这棵树上进行 $m$ 轮游戏,每轮游戏,小 A 和小 B 同时在树上选择一个之前未被选择的节点,小 A 只能选白点,小 B 只能选黑点。

假如在一轮游戏中,小 A 选 $u$、小 B 选 $v$,如果 $u,v$ 之间存在祖先后代关系,则该轮游戏是“有效的”,否则为“平局”。

对于每个 $i=0\text~m$,求有多少种游戏方案使得有效的轮数等于 $i$。

两个游戏方案 $X,Y$ 不同当且仅当 $X$ 中存在一轮游戏,小 A 选了 $u$、小 B 选了 $v$,并且 $Y$ 中不存在任何一轮游戏,小 A 选了 $u$、小 B 选了 $v$。

数据规模:$1\le n\le 5000$,保证 $n$ 为偶数。


# 解析

显然要 DP……

显然直接计算恰好 $i$ 轮有效游戏的方案数特别麻烦,不妨考虑计算大于等于 $i$ 轮有效游戏的方案数。这样定义有什么好处?我们发现如果是计算恰好 $i$ 轮游戏,我们不仅要决策哪几轮游戏是有效的,还要决策那些平局是怎样进行的(要保证除了我们决策到的有效游戏,其他游戏都是平局)……这样状态就复杂了

>Tab. tly dalao 说可以定义状态 f[u][i][j] 表示 $u$ 的子树中有 $i$ 轮游戏有效、$j$ 轮游戏平局的方案数。

而改了状态定义之后,我们可以只决策哪 $i$ 轮游戏是保证有效的,其他游戏只需要把剩下的点随便两两匹配,显然有效游戏的轮数只可能更多,也就是大于等于 $i$ 轮游戏有效。这样的话,状态就可以定义为 g[u][i] 表示 $u$ 的子树中,保证了至少有 $i$ 轮游戏有效的方案数。

这就很像背包问题——其实就是树上背包

考虑从子树 $v$ 转移到节点 $u$:

  1. 没有新增有效游戏:$g’_{u,i+j}\leftarrow g_{u,i}\times g_{v,j}$;
  2. 新增了一个有效游戏:如何能够新增?只能是 $v$ 中的一个未匹配的节点和节点 $u$ 匹配(当然 $u,v$ 颜色不同),于是开心地发现还需要再增加一维状态——g[u][i][0/1] 最后的 0/1 表示 $u$ 现在是否已经匹配,如果没有匹配,就可以和 $v$ 中的一个未匹配的异色节点匹配。

> Hint. 可以发现在更新 $u$ 的背包时,$v$ 子树中节点 $v$ 到底有没有被选并不重要

既然更改了状态,那么重新梳理一下:

  1. 没有新增有效游戏:

  2. $u$ 与子树 $v$ 中未匹配的异色节点匹配($cnt$ 是 $v$ 子树中与 $u$ 异色的未匹配节点的数量,这个先维护 $v$ 中黑白节点分别的数量然后直接计算):

这样我们可以决策出“已经进行了恰好 $i$ 轮有效游戏”的方案数 $(g_{1,i,0}+g_{1,i,1})$ ,还要把剩下的 $m-i$ 轮游戏进行完。因为状态的定义是至少 $i$ 轮,那么剩下的 $m-i$ 轮游戏到底是有效还是平局其实没有关系了,所以只需要把剩下的 $m-i$ 个黑点和 $m-i$ 个白点随意匹配即可,不难发现方案数是 $(m-i)!$,把 $(g_{1,i,0}+g_{1,i,1})\times(m-i)!$ 记作 $h_i$。

然后怎样通过 $g$ 倒回去求 $f$?显然一开始 $f_{1,m}=h_m$。不妨设我们现在已经求出了 $f_{1,i+1},f_{1,i+2},\dots,f_{1,m}$,要求 $f_{1,i}$。先给出结论:

什么意思呢?因为 $h_i$ 只是保证了已经有 $i$ 轮有效游戏,而 $f_{1,j}$ 是恰好进行了 $j$ 轮有效游戏,所以从 $f_{1,j}$ 的 $j$ 轮游戏中选出 $j$ 轮,就会被统计到 $h_i$ 中,因此 $f_{1,j}$ 对应的所有方案,在 $h_i$ 中实际上被计算了 $C_j^i$ 次。

因此


# 源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=5000,MOD=998244353;

int Add(int a,int b){return a+b>=MOD? a+b-MOD:a+b;}
int Sub(int a,int b){return a-b<0? a-b+MOD:a-b;}
int Mul(int a,int b){return 1ll*a*b%MOD;}
int QPow(int a,int b){
int ret=1;
while(b){
if(b&1) ret=Mul(ret,a);
a=Mul(a,a);
b>>=1;
}
return ret;
}

int n;
char sym[N+3];
int fac[N+3],inv[N+3];
int f[N+3][N+3][2],exf[N+3],cnt[N+3][2];
int tmp[N+3][2];

struct TREE{
int to[N*2+3],nxt[N*2+3],head[N+3],ncnt;
void AddEdge(int u,int v){
int p=++ncnt,q=++ncnt;
to[p]=v;nxt[p]=head[u];head[u]=p;
to[q]=u;nxt[q]=head[v];head[v]=q;
}
int operator [](int id){return head[id];}
}Tr;

int Ri(){
int a=0,c=getchar();
while(c<'0' || '9'<c) c=getchar();
while('0'<=c && c<='9'){
a=(a<<1)+(a<<3)+c-'0';
c=getchar();
}
return a;
}
void DP(int u,int fa){
cnt[u][sym[u]-'0']++;
for(int it=Tr[u];it;it=Tr.nxt[it]){
int v=Tr.to[it];
if(v==fa) continue;
DP(v,u);
cnt[u][0]+=cnt[v][0];
cnt[u][1]+=cnt[v][1];
}
int sizu=1;
f[u][0][0]=1;
for(int it=Tr[u];it;it=Tr.nxt[it]){
int v=Tr.to[it];
if(v==fa) continue;
int sizv=cnt[v][0]+cnt[v][1];
for(int i=0;i<=sizu/2;i++)
for(int j=0;j<=sizv/2;j++){
int fvj=Add(f[v][j][0],f[v][j][1]);
tmp[i+j][0]=Add(tmp[i+j][0],Mul(f[u][i][0],fvj));
tmp[i+j][1]=Add(tmp[i+j][1],Mul(f[u][i][1],fvj));
int tim=cnt[v][!(sym[u]-'0')];
tim-=j;
if(tim<0) continue;
tmp[i+j+1][1]=Add(tmp[i+j+1][1],Mul(f[u][i][0],Mul(fvj,tim)));
}
sizu+=sizv;
for(int i=0;i<=sizu/2;i++){
f[u][i][0]=tmp[i][0];
f[u][i][1]=tmp[i][1];
tmp[i][0]=tmp[i][1]=0;
}
}
}
int C(int a,int b){return Mul(fac[b],Mul(inv[a],inv[b-a]));}
int main(){
n=Ri();
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=Mul(fac[i-1],i);
inv[n]=QPow(fac[n],MOD-2);
for(int i=n-1;i>=0;i--) inv[i]=Mul(inv[i+1],i+1);
scanf("%s",sym+1);
for(int i=1;i<n;i++)
Tr.AddEdge(Ri(),Ri());
DP(1,0);
for(int i=0;i<=n/2;i++)
exf[i]=Mul(Add(f[1][i][0],f[1][i][1]),fac[n/2-i]);
for(int i=n/2;i>=0;i--){
for(int j=0;j<i;j++)
exf[j]=Sub(exf[j],Mul(exf[i],C(j,i)));
}
for(int i=0;i<=n/2;i++)
printf("%d\n",exf[i]);
return 0;
}

THE END

Thanks for reading!