【ARC101C】 Ribbons on Tree 题解 (容斥 + 树形 dp)

LG 传送门:AT4352 [ARC101C] Ribbons on Tree

先放兔队的题解。

然后解释一下细节:为什么可以每个连通块内随意连边?这就需要注意容斥的一大特点了——除了集合 \(S\) 内的边一定不能经过之外,其他的边没有经过与否的限制。这显然大大简化了求解难度。

《转移也是显然的》:(兔队题解中的 \(dp\) 数组使用了 \(f\) 数组替代。)

for(auto v : e[u]) if(v ^ fa){
	tree_dp(v, u); rep(i, 1, sz[u] + sz[v]) t[i] = 0;
	rep(i, 1, sz[u]) rep(j, 1, sz[v])
		t[i] = (t[i] + f[u][i] * f[v][j] % mod * g[j] % mod * (-1) % mod + mod) % mod,
		t[i + j] = (t[i + j] + f[u][i] * f[v][j] % mod) % mod;
	sz[u] += sz[v]; rep(i, 1, sz[u]) f[u][i] = t[i];

第四行的转移:假如节点 \(u\) 和儿子 \(v\) 强制性不连通,即两者之间的树边属于集合 \(S\)。然后再把 \(v\) 子树内的配对方案乘进去,因为 \(f_{v,j}\) 内统计的方案数本身并不包含 \(v\) 所在连通块的配对数,故要乘上 \(g_j\)

第五行的转移:然后就统计将 \(u\)\(v\) 连通的情况数。此时 \(v\) 所在的连通块就是 \(i\) 所在的连通块,所以不需要额外统计。

最后,因为我们统计方案数的时候已经把容斥系数一并算进去了,所以最后直接乘上匹配数再相加即可:rep(i, 1, n) ans = (ans + g[i] * f[1][i] % mod) % mod;


#include<bits/stdc++.h>
using namespace std;

#define int long long
#define rep(i, a, b) for(register int i = a; i <= b; ++i)
const int mod = 1e9 + 7, maxn = 5e3 + 5;
int n, u, v, ans, sz[maxn];
int f[maxn][maxn], g[maxn], t[maxn];
vector <int> e[maxn];

inline void tree_dp(int u, int fa){
	f[u][sz[u] = 1] = 1;
	for(auto v : e[u]) if(v ^ fa){
		tree_dp(v, u); rep(i, 1, sz[u] + sz[v]) t[i] = 0;
		rep(i, 1, sz[u]) rep(j, 1, sz[v])
			t[i] = (t[i] + f[u][i] * f[v][j] % mod * g[j] % mod * (-1) % mod + mod) % mod,
			t[i + j] = (t[i + j] + f[u][i] * f[v][j] % mod) % mod;
		sz[u] += sz[v]; rep(i, 1, sz[u]) f[u][i] = t[i];
	} 
}

signed main(){	
	scanf("%lld", &n); 
	rep(i, 2, n) scanf("%lld%lld", &u, &v),
		e[u].push_back(v), e[v].push_back(u);
	g[0] = 1; rep(i, 2, n) if(!(i & 1)) g[i] = g[i - 2] * (i - 1) % mod;
	tree_dp(1, 0); rep(i, 1, n) ans = (ans + g[i] * f[1][i] % mod) % mod;
	return printf("%lld\n", ans), 0;
}
posted @ 2022-09-15 21:27  pldzy  阅读(56)  评论(0)    收藏  举报