题目链接:https://ac.nowcoder.com/acm/problem/20811
题目大意:
给出n个点的一棵树,问有多少种删边方案使得删边后各连通块大小小于等于k?
题目思路:
考虑树形dp与组合数学结合
定义dp状态 dp(i,k) 代表 i的子树全部合法且i的连通块大小是k
那么显然对于任意一个节点u来说初始:dp[u][1] = 1
接下来枚举每一条边,对于一条边来言有删除与不删除两种状态:
1.删除:
删除此边,那么就意味着当前以u节点连通块大小为k的方案数 都可以 乘 v节点连通块大小所有的方案数:
2.不删除
不删除就相当于合并那么此时直接跑两个循环即可:
for(int i=1;i<=min(sz[u]*1ll,m);i++){
for(int k=1;k<=min(sz[e]*1ll,m);k++){
if(i+k<=m){
tmp[i+k] += (dp[u][i]*dp[e][k])%mod;
tmp[i+k] %=mod;
}
}
}
考虑到和dp[u][k]的状态有关,所以用tmp数组先预初储存一下!
Code:
/*** keep hungry and calm CoolGuang!***/
ll n,m,p;
ll dp[2005][2005];
vector<int>v[maxn];
int sz[maxn];
ll tmp[maxn];
void dfs(int u,int fa){
dp[u][1] = 1;
sz[u] = 1;
for(int e:v[u]){
if(e == fa) continue;
dfs(e,u);
ll sum = 0;
///连边
for(int i=1;i<=min(sz[u]*1ll,m);i++){
for(int k=1;k<=min(sz[e]*1ll,m);k++){
if(i+k<=m){
tmp[i+k] += (dp[u][i]*dp[e][k])%mod;
tmp[i+k] %=mod;
}
}
}
///不连边
for(int i=1;i<=min(m,sz[e]*1ll);i++) sum = (sum + dp[e][i])%mod;
for(int i=1;i<=m;i++){
dp[u][i] = (tmp[i] + sum*dp[u][i])%mod;
tmp[i] = 0;
}
sz[u] += sz[e];
}
}
int main(){
read(n);read(m);
for(int i=1;i<=n-1;i++){
ll x,y;read(x);read(y);
v[x].push_back(y);
v[y].push_back(x);
}
dfs(1,1);
ll ans = 0;
for(int k=1;k<=m;k++) ans = (ans + dp[1][k])%mod;
printf("%lld\n",ans);
return 0;
}
/***
3 3
1 2
1 3
***/
补充:
最后对于复杂度的证明:
我试过无数种写法,只有这一种在sz[e]和sz[u]的情况下过了
这种写法的复杂度其实是O(n^2)的
考虑lca的贡献次数即可