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