直接求是困难的,所以考虑容斥将所求容斥为两部分:每个结点至少在一棵树上为叶子的方案数 - 至少有一个结点在两棵树上都为叶子的方案数。
考虑 DP,设 \(f_i(x, y)\) 表示 \([1, i]\) 中是第一棵树的非叶子的结点数为 \(x\),\([i + 1, n]\) 中是第二棵树的非叶子的结点数为 \(y\) 时的方案数。
每次新增一个点,分讨转移:
- \(i\) 在第一棵树上是非叶子,在第二棵树上是叶子:
- \(i\) 在第一棵树上是叶子,在第二棵树上是非叶子:
-
\(i\) 在两棵树上都是叶子:
这里其实还有两种情况:
- \(i\) 在第一棵树上是叶子,钦定它在第二棵树上是叶子。
- \(i\) 在第二棵树上是叶子,钦定它在第一棵树上是叶子。
但因为结果相同,所以把两种情况并在一起转移,即
初值:\(\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;
}
标签:结点,P8329,int,一棵树,times,叶子,ZJOI2022,gets
From: https://www.cnblogs.com/chy12321/p/18026056