【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;
}

浙公网安备 33010602011771号