【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 子树内的配对方案乘进去,因为 fv,j 内统计的方案数本身并不包含 v 所在连通块的配对数,故要乘上 gj

第五行的转移:然后就统计将 uv 连通的情况数。此时 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 @   pldzy  阅读(38)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示