Luogu P1272 重建道路 (树形dp)

image

  • 树上背景,要凑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;
}
posted @ 2022-04-09 21:39  qingyanng  阅读(25)  评论(0编辑  收藏  举报