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