蓝魔法师 | 树形dp、组合数学
蓝魔法师
https://ac.nowcoder.com/acm/problem/20811
考虑树形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 ***/
PS:最后,关于这个复杂度是n^2的,考虑任意两点组合会在该lca上合并一次其余不会在别的根上合并,所以复杂度整体n^2