[ZJOI2022] 树

有两棵 \(n\) 个节点的有根树。

第一棵树 \(T_1\) 的根节点为 \(1\)\(i\) 的父亲节点 \(\in [1,i-1]\)

第二棵树 \(T_2\) 的根节点为 \(n\)\(i\) 的父亲节点 \(\in [i+1,n]\)

\(T_1\) 的非叶子节点构成的集合为 \(S_1\)\(T_2\) 的非叶子节点构成的集合为 \(S_2\)

求不同的 \((T_1,T_2)\) 总数,使得 \(S_1 \cap S_2 = \varnothing\)\(S_1 \cup S_2 = \{1,2,\dots,n\}\)

你需要对 \([2,N]\) 中的每个 \(n\) 求出答案,对 \(M\) 取模。

\(2 \leq N \leq 500\)\(10 \leq M \leq 2^{30}\)


考虑容斥。

\(f_1(S)\) 表示 \(T_1\) 的非叶子节点构成的集合 \(S_1 = S\) 时,对应的 \(T_1\) 方案数。

同理定义 \(f_2(S)\),此时答案写成:

\[\sum\limits_{S_1 \cap S_2 = \varnothing,S_1 \cup S_2 = \{1,2,\dots,n\}} f_1(S_1) f_2(S_2) \]

考虑进行一点容斥。

\(f_1'(S)\) 表示 \(T_1\) 的非叶子节点构成的集合 \(S_1 \subseteq S\) 时,对应的 \(T_1\) 方案数。

同理,定义 \(f_2'(S)\),此时答案写成:

\[\sum\limits_{S_1 \cap S_2 = \varnothing,S_1 \cup S_2 = \{1,2,\dots,n\}} \sum\limits_{S_1' \subseteq S_1,S_2' \subseteq S_2} f_1'(S_1')f_2'(S_2') (-1)^{|S_1|+|S_2|-|S_1'|-|S_2'|} \]

此时交换求和顺序,得:

\[\sum\limits_{S_1' \cap S_2' = \varnothing} f_1'(S_1')f_2'(S_2') (-1)^{n - |S_1'| - |S_2'|}2^{n - |S_1'| - |S_2'|} \]

即:

\[\sum\limits_{S_1 \cap S_2 = \varnothing} f_1'(S_1)f_2'(S_2) (-2)^{n - |S_1| - |S_2|} \]

接下来考虑计算这个东西。

我们令 \(dp_{i,j,k}\) 表示确定了 \(\{1,2,\dots,i\}\) 中的点在 \(S_1\)\(S_2\) 中的情况,且:

  • \(|\{1,2,\dots,i\} \cap S_1| = j\)

  • \(|\{i+1,i+2,\dots,n\} \cap S_2| = k\)

此时确定 \((1,i]\) 在第一棵树中的父亲,\([1,i)\) 在第二棵树中的父亲,总共的方案数。

显然,初始情况是 \(dp_{1,1,k} = 1\),接下来考虑 \(dp_{i-1,j,k}\) 的转移。

我们考虑 \(i\) 在树上的情况,显然有:

  • \(i\) 属于 \(S_1\),此时 \(dp_{i-1,j,k}\) 可以转移到 \(dp_{i,j+1,k}\),系数是 \(j \times k\)

  • \(i\) 属于 \(S_2\),此时 \(dp_{i-1,j,k}\) 可以转移到 \(dp_{i,j,k-1}\),系数是 \(j \times k\)

  • \(i\) 不属于 \(S_1\)\(S_2\),此时 \(dp_{i-1,j,k}\) 可以转移到 \(dp_{i,j,k}\),系数是 \(-2 \times j \times k\)

为什么是 \(-2\),因为这样才能保证在上面的式子中,\(-2\) 的次数是 \(n - |S_1| - |S_2|\)

同时,\(j\) 的乘积表示 \(f_1'(S_1)\)\(k\) 的乘积表示 \(f_2'(S_2)\)

复杂度 \(O(n^3)\)

最后算答案,考虑 \(n=i\) 对应的答案,显然是 \(dp_{i-1,j,k}\) 只进行第二种转移的结果。

同时,因为是答案,所以 \(k=1\)

这状态是咋想到的,位居了。

//Ad astra per aspera
#include<iostream>
#include<cstdio>
using namespace std;
long long mod;
long long dp[510][510],new_dp[510][510];
int main(){
	int n;
	scanf("%d %lld",&n,&mod);
	for(int i=1;i<n;i++){
		dp[1][i]=1;
	}
	for(int i=2;i<=n;i++){
		for(int j=1;j<=i;j++){
			for(int k=1;k<=n-i;k++){
				new_dp[j][k]=0;
			}
		}
		long long ans=0;
		for(int j=1;j<=i-1;j++){
			for(int k=1;k<=n-i+1;k++){
				//i in s1
				if(j+1<=i){
					new_dp[j+1][k]+=(long long)j*k*dp[j][k]%mod;
					new_dp[j+1][k]%=mod;
				}
				if(k-1>=1){
					new_dp[j][k-1]+=(long long)j*k*dp[j][k]%mod;
					new_dp[j][k-1]%=mod;
				}
				new_dp[j][k]-=(long long)2*j*k*dp[j][k]%mod;
				new_dp[j][k]=(new_dp[j][k]%mod+mod)%mod;
				if(k==1){
					ans+=(long long)j*k*dp[j][k]%mod;
					ans%=mod;
				}
			}
		}
		for(int j=1;j<=i;j++){
			for(int k=1;k<=n-i;k++){
				dp[j][k]=new_dp[j][k];
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2026-01-14 20:36  Oken喵~  阅读(3)  评论(1)    收藏  举报