P8329 [ZJOI2022] 树
直接求是困难的,所以考虑容斥将所求容斥为两部分:每个结点至少在一棵树上为叶子的方案数 - 至少有一个结点在两棵树上都为叶子的方案数。
考虑 DP,设 \(f_i(x, y)\) 表示 \([1, i]\) 中是第一棵树的非叶子的结点数为 \(x\),\([i + 1, n]\) 中是第二棵树的非叶子的结点数为 \(y\) 时的方案数。
每次新增一个点,分讨转移:
- \(i\) 在第一棵树上是非叶子,在第二棵树上是叶子:
\[f_i(x, y) \gets f_i(x, y) + f_{i-1}(x-1, y) \times (x-1)y
\]
- \(i\) 在第一棵树上是叶子,在第二棵树上是非叶子:
\[f_i(x, y) \gets f_i(x, y) + f_{i-1}(x, y+1) \times xy
\]
-
\(i\) 在两棵树上都是叶子:
这里其实还有两种情况:
- \(i\) 在第一棵树上是叶子,钦定它在第二棵树上是叶子。
- \(i\) 在第二棵树上是叶子,钦定它在第一棵树上是叶子。
但因为结果相同,所以把两种情况并在一起转移,即
\[f_i(x, y) \gets f_{i-1}(x, y) \times (-2xy)
\]
初值:\(\forall y \in [1, n - 1], f_1(1, y) = y\)。
答案:\(ans_n = \sum\limits_{i=1}^{n-2} f_{n-1}(x, 1) \times x\)。
时间复杂度 \(\mathcal O(n^3)\)。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
constexpr int N = 510;
int n, MOD;
ll pf[N][N], cf[N][N];
int main() {
ios_base::sync_with_stdio(0); cin.tie(nullptr), cout.tie(nullptr);
cin >> n >> MOD;
for (int i = 1; i < n; i++) pf[1][i] = i;
for (int i = 2; i <= n; i++) {
ll ans = 0;
for (int x = 1; x < i; x++) ans += pf[x][1] * x;
cout << (ans % MOD + MOD) % MOD << '\n';
for (int x = 1; x <= i; x++) {
for (int y = 1; x + y <= n; y++) {
cf[x][y] = (pf[x - 1][y] * (x - 1) * y + pf[x][y + 1] * x * y - ((pf[x][y] * x * y) << 1)) % MOD;
}
}
memcpy(pf, cf, sizeof(pf));
}
return 0;
}