Luogu P1272 重建道路 (树形dp)
- 树上背景,要凑p个点,我们想到背包。 但是这个体比较异类,要删点
- 还是背包那样dp[j] <- dp[j - k] + dp[k], 显然是f[u][j] = min(f[u][j], f[u][j - k] + f[v][k] - 1);表示以u为根的子节点保留j个点再本棵子树最少删的边
- 考虑边界和初始化,从小的转移,就是初始j为 0和1, 保留0个点删的边是u到他父亲的边,再u根子树中删0个,保留1个点就是删除所以u的儿子v与u的连边。删sons[u] - 1边。
- 除了树真正根以外,其他的u根的答案到要加1,减去u到fa的边。
总结:树形dp计算贡献,只计算以u为根的子树中的贡献,若操作是和边有关的,比如删边:那么考虑u子树就有个默认前提:u必选,所以接下来枚举k必须比j少1留给u。
这时dp[u][0]不能随便转移,很可能是不合法的
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e2 + 5;
const int mod = 1e9 + 7;
int n, p;
int h[N], e[N << 1], ne[N << 1], idx;
int sons[N], sz[N];
int f[N][N]; bool st[N];
void add( int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs( int u, int fa ) {
f[u][0] = 0, f[u][1] = sons[u];
sz[u] = 1;
for ( int i = h[u]; ~i; i = ne[i] ) {
int v = e[i];
dfs(v, u);
sz[u] += sz[v];
for ( int j = sz[u]; j >= 1; -- j) { //j从0开始, k从0开始都是不对的
for ( int k = 1; k < min(sz[v] + 1, j); ++ k) {
f[u][j] = min(f[u][j], f[u][j - k] + f[v][k] - 1); // 必须留一个1给根
}
}
}
}
int main () {
memset(h , -1, sizeof h);
memset(f , 0x3f, sizeof f);
cin >> n >> p;
for ( int i = 1; i <= n - 1; ++ i ) {
int u, v; cin >> u >> v;
add(u, v);
++ sons[u]; st[v] = 1; //找出没有入度的点
}
int root = 1;
for ( int i = 1; i <= n; ++ i ) {
if(st[i]) continue;
root = i; break;
}
dfs(root, -1);
int ans = f[root][p];
for ( int i = 1; i <= n; ++ i ) {
if(f[i][p] + 1 < ans) {
ans = f[i][p] + 1;
}
}
cout << ans << '\n';
return 0;
}