题意: 给定一棵个点的树,问有多少种删边方案使得删边后每个连通块大小不大于
,两种方案不同当且仅当存在一条边在一个方案中被删除,而在另一个方案中未被删除,答案对
取模
数据范围:
题解 :表示以
为根,连通块的大小为
的方案数。
考虑的一个儿子
:
和
的边被断开,则
的连通块大小对
无影响,
与
的边未被断开,则
可以注意到这是一种背包,关于
选或不选的问题。
普通背包第一层是枚举物品种类,这里即枚举第几个
。
第二层枚举体积,这里即枚举连通块大小,故先逆序枚举的连通块大小,然后枚举(不考虑顺序)子集连通块来更新即可。
这里需要注意在枚举时,保证当前的
中没有出现过
,否则会出现重复方案。
代码:
#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; }