[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\)。
- 钦定 \(i\) 不是最大值。枚举一个儿子 \(p\) 提供最大值,枚举最大值 \(v_1\) 和 \(i\) 的赋值 \(v_2\)。
预处理 \(pre_{i, v}\), \(suf_{i, v}\) 表示前缀后缀 \(g_{q, v}\) 的乘积。设 \(p\) 在 \(Son_{i}\) 中编号为 \(u\)。
那么转移变成了:
但是这样复杂度还是 \(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)\)。
然后转移就变成了:
这样转移就变成 \(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;
}

浙公网安备 33010602011771号