Liveddd's Blog

愛にできることはまだあるかい

P8329 [ZJOI2022] 树

好的容斥题目。

容易发现DP过程中直接钦定叶子是不好做的,并且需要满足并集为全集。很容易想到使用容斥去掉这一条件。我们对于非叶子进行考虑:设 $f(S)$ 表示第一棵树中集合 $S$ 为非叶子节点时的方案数, $g(T)$ 表示第二棵树中集合 $T$ 为非叶子节点时的方案数,得到答案为:

直接容斥,设 $f’(S)$ 表示第一棵树中集合 $S’\subseteq S$ 为非叶子节点时的方案数, $g(T)$ 表示第二棵树中集合 $T’\subseteq T$ 为非叶子节点时的方案数,进一步得到:

我们成功将“并集为全集”这一条件去掉。并且这是容易DP的。影响决策的只有 $|S’|,|T’|$,将其压入状态,设 $f(x,i,j)$ 表示考虑到 $x$,$S’$ 有 $i$ 个元素,$T’$ 有 $j$ 个的元素带容斥系数的方案数。我们顺序枚举所有元素,将两棵树以不同的顺序 DP,设边界为 $f(1,1,i)=1$,最终答案为 $\sum_i f(n,i,1)\times i$。考虑转移。对于当前枚举的元素 $x$,可以选择 $S’$ 中任意元素作为第一棵树上的父亲,和选择 $T’$ 中任意元素作为第二棵树上的父亲。所以转移系数为 $i\times j$。现在考虑是否将 $x+1$ 放进 $S’,T’$ 里,分类讨论一下:

$(1).x\in S’$,钦定 $x+1$ 为第二棵树的叶子, $i\times j\times f(x,i,j)\to f(x+1,i+1,j)$。

$(2).x\in T’$,钦定 $x+1$ 为第一棵树的叶子, $i\times j\times f(x,i,j)\to f(x+1,i,j-1)$。

$(3).x\notin S’,x\notin T’$,钦定 $x+1$ 为第一棵树的叶子和第二棵树的叶子,$-2\times i\times j\times f(x,i,j)\to f(x+1,i,j)$。

需要滚动数组优化空间。

Code
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
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=500+10;
template <class T>
inline void read(T &x)
{
x=0;bool f=0;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
if(f)x=-x;
}
template <class T,class ...T1>
inline void read(T &x,T1 &...x1)
{
read(x),read(x1...);
}
int n,mod;
int p,f[2][N][N];
inline void upd(int &x,int y){x+=y;x=(x>=mod)?x-mod:x;}

int main()
{
read(n,mod);
for(int i=1;i<n;i++)f[p][1][i]=1;
for(int x=1;x<n;x++,p^=1)
{
memset(f[p^1],0,sizeof(f[p^1]));
int ans=0;
for(int i=1;i<=x;upd(ans,1ll*i*f[p][i][1]%mod),i++)
for(int j=1;j<=n-x;j++)
{
upd(f[p^1][i+1][j],1ll*i*j*f[p][i][j]%mod);
upd(f[p^1][i][j-1],1ll*i*j*f[p][i][j]%mod);
upd(f[p^1][i][j],1ll*i*j*(mod-2)%mod*f[p][i][j]%mod);
}
printf("%d\n",ans);
}
return 0;
}