题意: 给定一棵个点的树,问有多少种删边方案使得删边后每个连通块大小不大于,两种方案不同当且仅当存在一条边在一个方案中被删除,而在另一个方案中未被删除,答案对取模
数据范围:

题解 :
表示以为根,连通块的大小为的方案数。
考虑的一个儿子

  • 的边被断开,则的连通块大小对无影响,
  • 的边未被断开,则

可以注意到这是一种背包,关于选或不选的问题。
普通背包第一层是枚举物品种类,这里即枚举第几个
第二层枚举体积,这里即枚举连通块大小,故先逆序枚举的连通块大小,然后枚举(不考虑顺序)子集连通块来更新即可。
这里需要注意在枚举时,保证当前的中没有出现过,否则会出现重复方案。

代码:

#include<bits/stdc++.h>
using namespace std;

const int N = 2010;
const int mod = 998244353;
vector<int> g[N];
int n, k;
int f[N][N];
int siz[N];
void dfs(int u, int fa) {
    siz[u] = 1;
    f[u][1] = 1;
    for(auto v : g[u]) {
        if(v == fa) continue;
        dfs(v, u);

        //断边预处理 
        int sum = 0;
        for(int i = 1; i <= min(siz[v], k); i++) sum = (sum + f[v][i]) % mod;

        //不断边 
        for(int i = min(siz[u], k); i >= 1; i--) {
            for(int j = min(siz[v], k); j >= 1; j--)
                if(i + j <= k)
                    f[u][i + j] = (f[u][i + j] + 1ll * f[u][i] * f[v][j] % mod) % mod;
            //断边 
            f[u][i] = 1ll * f[u][i] * sum % mod;
        }
        siz[u] += siz[v];  
    }
}

int main()
{
    scanf("%d%d", &n, &k);
    for(int i = 1; i < n; i++) {
        int a, b;
        scanf("%d%d", &a, &b);
        g[a].push_back(b);
        g[b].push_back(a);
    }    

    dfs(1, 0);

    int res = 0;
    for(int i = 1; i <= k; i++)
        res = (res + f[1][i]) % mod;
    printf("%d\n", res);

    return 0; 
}