[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; 
}
上一篇:牛客题霸--买卖股票的最好时机


下一篇:AtCoder Regular Contest 108