[ARC101C] Ribbons On the Tree
[题目链接]
https://atcoder.jp/contests/arc101/tasks/arc101_c
[题解]
直接做比较麻烦。
考虑一个最简单的容斥原理 : \(ans = \sum{(-1)^{|S|}f(S)}\) , 其中 \(f(S)\) 表示 \(S\) 集合内所有边都不选的方案。
考虑对于一个大小为 \(N\) (\(N\) 为偶数) 的联通块随便连的方案数 , 其贡献是 \((N - 1) * (N - 3) * ... * 1\)。 那么 \(f(S)\) 就是分割成的联通块的贡献乘积。
不妨用 \(dp(i , j)\) 表示以 \(i\) 为根的子树 , 现在 \(i\) 所在联通块大小是 \(j\) 的贡献和 (不包括当前联通块的贡献)。 转移的时候乘上容斥系数。 那么只需要做一个简单的树上背包即可。
时间复杂度 : \(O(N ^ 2)\)
[代码]
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MN = 5005 , mod = 1e9 + 7;
int N , coef[MN] , f[MN][MN] , siz[MN];
vector < int > a[MN];
inline void inc(int &x , int y) {
x = x + y < mod ? x + y : x + y - mod;
}
inline void dec(int &x , int y) {
x = x - y >= 0 ? x - y : x - y + mod;
}
inline void solve(int u , int fa) {
f[u][1] = 1; siz[u] = 1;
for (int v : a[u])
if (v != fa) {
solve(v , u);
static int g[MN];
for (int i = 1; i <= siz[u] + siz[v]; ++i) g[i] = 0;
for (int i = 1; i <= siz[u]; ++i) {
for (int j = 1; j <= siz[v]; ++j) {
dec(g[i] , 1ll * f[u][i] * coef[j] % mod * f[v][j] % mod);
inc(g[i + j] , 1ll * f[u][i] * f[v][j] % mod);
}
}
siz[u] += siz[v];
for (int i = 1; i <= siz[u]; ++i) f[u][i] = g[i];
}
}
int main() {
scanf("%d" , &N);
for (int i = 1; i < N; ++i) {
int u , v;
scanf("%d%d" , &u , &v);
a[u].emplace_back(v) , a[v].emplace_back(u);
}
coef[0] = 1;
for (int i = 2; i <= N; i += 2) coef[i] = 1ll * coef[i - 2] * (i - 1) % mod;
solve(1 , 0); int ans = 0;
for (int i = 2; i <= N; i += 2)
inc(ans , 1ll * coef[i] * f[1][i] % mod);
printf("%d\n" , ans);
return 0;
}