[CF1707D] Partial Virtual Trees 题解(容斥,树形dp)

题面

题意:
给定一个 \(n\) 个点的树以及质数 \(p\),对每个 \(k = 1,2,..,n-1\) 计算满足下述条件的点集序列
\(\{S_i\}_{i = 1}^{k}\) 的数量 \(mod\) \(p\)

  • \(\{1\} = S_k⊊ S_{k - 1} ⊊...⊊S_{0} = \{1,2,..,n\}\)
  • 对每个 \(S_i\) 以及 \(u, v \in S_i\),都有 \(LCA(u, v) \in S_i\)

其中 \(LCA(u, v)\) 表示以 \(1\) 为根时 \(u\)\(v\) 的最近公共祖先。

数据规模:
\(2 \leq n \leq 2000\)\(10^8 + 7 \leq p \leq 10^9 + 9\)\(p\) 是一个质数。

分析:
*3000 分,但是感觉是套路题。
首先第二个条件可以翻译成 任一时刻点集 \(S_i\) 中的点都构成了一棵虚树
遇到这种每次变成原来的一个子集,让对合法的集合序列计数的问题。可以考虑 给每个元素分配一个存在的时间,也就是对应什么时刻这个元素将会从集合里消失。

\(t_i\) 表示 \(i\) 号点在集合序列中第 \(t_i\) 个位置第一次消失,也就是 \(S_{t_i}\) 第一次不包含 \(i\) 号点。
那么对于一个 \(k\),考虑 \(\{t_i\}\) 应满足什么条件能使构造出来的 \(S_0,S_1,...,S_k\) 合法:

  • \(t_2,...,t_n\)\([1, k]\) 范围内,并且 \(1,2,...,k\) 应该至少出现 \(1\) 次。
  • \(mx_v\) 表示 \(v\) 及其子树中 \(t\) 的最大值。对于一个节点 \(x(x \geq 2)\) 而言, \(t_x\) 应该 大于等于 \(\{mx_p\}(p \in Son_x)\)非严格次大值

其中第一个条件是为了保证 \(S_i\)\(S_{i - 1}\)真子集。第二个条件是为了满足 任一时刻点集 \(S_i\) 中的点都构成了一棵虚树

不难发现上面的条件是 \(S\) 序列合法的 充要条件,并且符合条件的 \(\{t_i\}\) 与 合法的 \(S\) 序列形成 双射 关系。因此我们只需要对符合条件的 \(\{t_{i}\}\) 计数即可。

发现有第一个条件不太好做,我们考虑将 \(1,2,...,k\) 都出现至少一次的限制容斥掉。
枚举一个 \(t\) 表示钦定 \(t\) 个数一次也不出现,其余数任意。最后将方案乘上 \((-1)^{t}\binom{k}{t}\) 累加即可。

那么现在问题转化成了:你可以给树上除了 \(1\) 外 每个点赋一个 \([1, c]\) 以内的值,需要满足 \(t_{x}\) 大于等于 \(\{mx_p\}(p \in Son_x)\) 的非严格次大值,求合法的赋值方案数。

这个可以直接树形 \(dp\)
\(f_{i, x}\) 表示考虑了以 \(i\) 为根的子树,\(t\) 的最大值为 \(x\) 的方案数。
\(g_{i, x}\) 表示考虑了以 \(i\) 为根的子树,\(t\) 的最大值小于等于 \(x\) 的方案数。
\(i\) 的儿子集合为 \(Son_i\)
转移分两种:

  • 钦定 \(i\) 是最大值。枚举一个值 \(v\) 表示给 \(i\) 的赋值,那么我们需要所有儿子子树的最大值小于等于 \(v\)

\[f_{i, v} \gets \prod\limits_{p\in Son_i} g_{p, v} \]

  • 钦定 \(i\) 不是最大值。枚举一个儿子 \(p\) 提供最大值,枚举最大值 \(v_1\)\(i\) 的赋值 \(v_2\)

\[f_{i, v_1} \gets f_{p, v_1} \sum\limits_{v_2 <v_1}\prod\limits_{q\in Son_i 且 q \ne p} g_{q, v_2} \]

预处理 \(pre_{i, v}\)\(suf_{i, v}\) 表示前缀后缀 \(g_{q, v}\) 的乘积。设 \(p\)\(Son_{i}\) 中编号为 \(u\)
那么转移变成了:

\[f_{i, v_1} \gets f_{p,v_1}\sum\limits_{v_2 <v_1}pre_{u - 1, v_2} \times suf_{u + 1, v_2} \]

但是这样复杂度还是 \(O(n^3)\) 的。
注意到没必要对每个 \(v_1\) 都从 \(1\) 开始扫一遍 \(v_2\),因此可以先枚举一个儿子 \(p\)(编号为 \(u\)),从小到大枚举 \(v\),求出 \(h_{u, v}\) 表示 \(pre_{u - 1, v} \times suf_{u + 1, v}\) 的前缀和。处理 \(h_{u, v}\) 的复杂度为 \(O(n^2)\)
然后转移就变成了:

\[f_{i, v_1} \gets f_{p,v_1} \times h_{p, v_1 - 1} \]

这样转移就变成 \(O(n^2)\) 了。求出 \(f\) 后做一遍前缀和即可得到 \(g\)
\(1\) 的转移特殊处理,即可得到 \(f_{1, v}\) 表示 \([1, v - 1]\) 的数给 \(2 \sim n\) 号点赋值的合法方案数

求答案时枚举一个 \(k\) 然后 \(O(n)\) 的容斥。
总复杂度 \(O(n^2)\)

CODE:

#include<bits/stdc++.h>
#define pb emplace_back
using namespace std;
const int N = 2005;
typedef long long LL;
int n;
LL mod, fac[N], inv[N];
vector< int > E[N];
LL f[N][N], g[N][N], h[N][N]; // f[i][j] 表示考虑了以 i 为根的子树,并且当前子树里面的最大值为 j 的方案数。 
LL pre[N][N], suf[N][N], t[N];
inline LL Pow(LL x, LL y) {
	LL res = 1LL, k = x;
	while(y) {
		if(y & 1) res = res * k % mod;
		y >>= 1;
		k = k * k % mod;
	}
	return res;
}
inline LL C(int n, int m) {
	if(n < m) return 0;
	return fac[n] * inv[m] % mod * inv[n - m] % mod;
} 
inline LL sign(int x) {return (x & 1) ? mod - 1LL : 1LL;}
void dfs(int x, int fa) {
	for(auto v : E[x]) {
		if(v == fa) continue;
		dfs(v, x);
	}
	int sz = E[x].size();
	for(int v = 1; v <= n; v ++ ) {
		pre[0][v] = 1LL;
		for(int i = 1; i <= sz; i ++ ) {
			int son = E[x][i - 1];
			pre[i][v] = pre[i - 1][v];
			if(son == fa) continue;
			pre[i][v] = pre[i][v] * g[son][v] % mod;
		}	
		suf[sz + 1][v] = 1LL;
		for(int i = sz; i >= 1; i -- ) {
			int son = E[x][i - 1];
			suf[i][v] = suf[i + 1][v];
			if(son == fa) continue;
			suf[i][v] = suf[i + 1][v] * g[son][v] % mod;
		}
	}
	for(int i = 1; i <= sz; i ++ ) {
		int son = E[x][i - 1];
		if(son == fa) continue;
		for(int v = 1; v <= n; v ++ ) {
			h[i][v] = h[i][v - 1];
			h[i][v] = (h[i][v] + pre[i - 1][v - 1] * suf[i + 1][v - 1] % mod) % mod;
		}
	}
	if(x != 1) {
		for(int v = 1; v <= n; v ++ ) { // 先转移 x 作为最大值的情况 
		    LL res = 1LL;
			for(int j = 1; j <= sz; j ++ ) {
				int son = E[x][j - 1];
				if(son == fa) continue;
				res = res * g[son][v] % mod;
			}
			f[x][v] = res;
		}
		for(int v = 1; v <= n; v ++ ) { // x 不是最大值 
		    for(int p = 1; p <= sz; p ++ ) {
		    	int son = E[x][p - 1]; // 钦定 son 是最大值 v 
		    	if(son == fa) continue; 
		    	f[x][v] = (f[x][v] + f[son][v] * h[p][v] % mod) % mod;
			}
		}
		for(int v = 1; v <= n; v ++ ) {
			g[x][v] = (g[x][v - 1] + f[x][v]) % mod;
		}
	}
	else {
		for(int v = 1; v <= n; v ++ ) {
			f[x][v] = 1LL;
			for(int p = 1; p <= sz; p ++ ) {
				int son = E[x][p - 1];
				f[x][v] = f[x][v] * g[son][v - 1] % mod;
			}
		}
	}
}
int main() {
	scanf("%d%lld", &n, &mod);
	fac[0] = 1LL; for(int i = 1; i <= n; i ++ ) fac[i] = fac[i - 1] * i % mod;
	inv[n] = Pow(fac[n], mod - 2LL);
	for(int i = n - 1; i >= 0; i -- ) inv[i] = inv[i + 1] * (i + 1) % mod;
	for(int i = 1; i < n; i ++ ) {
		int u, v; scanf("%d%d", &u, &v);
		E[u].pb(v); E[v].pb(u);
	}
	dfs(1, 0);	
	for(int i = 1; i < n; i ++ ) {
		LL res = 0;
		for(int j = 0; j <= i; j ++ ) {
			res = (res + C(i, j) * sign(j) % mod * f[1][i + 1 - j] % mod) % mod;
		}
		printf("%lld ", res);
	}
	return 0;
}
posted @ 2026-09-12 10:01  Czl2007  阅读(5)  评论(0)    收藏  举报