一棵树种保留1、2、3、……、n个连通块的方案数分别是多少?
显然树形DP,只是需要注意枚举的顺序、细节问题。
1、由背包枚举顺序可知,应从大到小枚举容量,保证用到的都是上次的。
2、子树b不需要枚举0个连通块,0个连通块就是自己本身的方案。
3、顺推不需要管太多细节,且枚举范围会小一些;倒退因为a和b都选,会少一个连通块,出现一些不好维护的顺序问题。

#include <stdio.h>
const int N=5005, mo=998244353;
int n, m, i, j, k, a, b, h[N], s[N];
long long f[N][N], g[N][N];
struct AB{
    int a, b, n;
} d[N*2];
void cun(int a, int b){
    d[++k].a = a, d[k].b = b;
    d[k].n = h[a], h[a] = k;
}
void dfs(int a, int p){
    int b, i, j, k;
    f[a][1] = g[a][0] = s[a] = 1;
    for(i=h[a]; i; i=d[i].n){
        b = d[i].b;
        if(b == p) continue;
        dfs(b, a);
        for(j=s[a]; j>=0; j--){
            for(k=s[b]; k; k--){
                f[a][j+k] += f[a][j] * g[b][k];
                f[a][j+k-1] += f[a][j] * f[b][k];
                f[a][j+k] %= mo, f[a][j+k-1] %= mo;
                g[a][j+k] += g[a][j] * f[b][k];
                g[a][j+k] += g[a][j] * g[b][k];
                g[a][j+k] %= mo;
            }//凑出j+k个连通块的方案数可以累加
        }//当前子树选择j个连通块,儿子选择k个连通块
        s[a] += s[b];//子树大小
    }
}//f[i][j]子树i有j个连通块,f是选i,g是不选i
int main(){
    scanf("%d", &n);
    for(i=1; i<n; i++){
        scanf("%d%d", &a, &b);
        cun(a, b);
        cun(b, a);
    }
    dfs(1, 0);
    for(i=1; i<=n; i++){
        printf("%lld\n", (f[1][i]+g[1][i])%mo);
    }
    return 0;
}

作者 crxis

发表回复