蓝魔法师 | 树形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

全部评论
大佬,那个复杂度分析可以详细讲一下吗?感觉那个证明好迷
1 回复
分享
发布于 2020-08-16 09:50

相关推荐

6 收藏 评论
分享
牛客网
牛客企业服务