Rebuilding Roads 【POJ - 1947】【树形DP or 树形背包】

题目链接

   很好的一道题,WA了几次之后算是对树形DP有了些自己的理解,题中给你N个节点,建树构成(N-1)条边,其中,问你要是只保留M个节点,需要至少删除几条的边?

   根据推断,这是一道不容置疑的树形DP,也可以说是树形背包,具体是什么原因,让我们在解题的过程中来理解其因。

   我们搜到最底的叶子节点,它的节点数就是它本身,所以我们对于其定义为其实节点,我们从它开始往上递推,到达第二个节点,有保留为两个节点或者删除其子节点就留一个节点,所以初始化的时候放上一个假如删到以它为根节点且只剩它本身的话,需要删除的是它的所有子节点,然后,假如向上,一直达到第三个节点的时候呢,那么他可能就会有很多子节点了,我们需要进行这样的处理了,还是从只有它本身开始看起,因为只有它本身的时候,可以看成一种初始状态,然后相当于往其中填充节点,所以,既然是填充,相当于背包,我们不能填充重复的节点,故,我们从大到小的进行填充,一个是把所有的这个节点都放进去,那么就是满的,但是这样呢,会发现就多一步,因为这时候的操作是这样的:dp[u][son+1]=min(dp[u][son+1], dp[u][1]+dp[v][son]-1);因为du[u][1]的时候,我们已经删去了v这号节点,现在加回来,就是少用了一步,所以-1。

   类似的,我们能继续往下推,推出完整的状态转移方程,或者说是背包方程,当然,我们填充完了其中一个子节点,那么我们填充上它的下一个子节点也是一样的道理,与前一个子节点不冲突,我们依旧执行背包操作,从此时满情况的来看,会是从这样的方程开始推的:(son1是前一个子节点、son2是目前这个子节点)dp[u][1+son1+son2]=min(dp[u][1+son1+son2], dp[u][1+son1]+dp[v][son2]-1);同理,因为补充上去的一个新的节点,所以要“-1”。后面的思维是一样的,所以,我给大家列写出来树形DP的状态转移方程:

状态转移方程

  • 初始化:dp[u][1]=vt[u].size();
  • dp[u][j]=min(dp[u][j], dp[u][j-k]+dp[v][k]-1);
  • 最后,我们只需要遍历所有的后续值为M的DP节点即可,当然,除了最最最前面的根节点“1”以外,其他的节点在做比较之时需要加上去除它的父节点相连的边的那一步“+1”。

 

完整代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxN=155;
int N, M;
vector<int> vt[maxN];
int dp[maxN][maxN];     //以i为根是,要保留j个节点需要付出的代价
int dfs(int u)
{
    int son=1;
    int len=(int)vt[u].size();
    dp[u][1]=len;
    for(int i=0; i<len; i++)
    {
        int v=vt[u][i];
        son+=dfs(v);
        for(int j=son; j>=1; j--)       //背包,倒叙
        {
            for(int k=1; k<j; k++)
            {
                dp[u][j]=min(dp[u][j], dp[u][j-k]+dp[v][k]-1);
            }
        }
    }
    return son;
}
int main()
{
    while(scanf("%d%d", &N, &M)!=EOF)
    {
        for(int i=1; i<=N; i++) vt[i].clear();
        memset(dp, INF, sizeof(dp));
        for(int i=1; i<N; i++)
        {
            int e1, e2;
            scanf("%d%d", &e1, &e2);
            vt[e1].push_back(e2);
        }
        dfs(1);
        int ans=dp[1][M];
        for(int i=2; i<=N; i++)
        {
            ans=min(ans, dp[i][M]+1);
        }
        printf("%d\n", ans);
    }
    return 0;
}

 

   初次将树形DP写自己的思考,也算是因为理解到了些东西做下的笔记,若有什么错误的想法,欢迎大佬指教。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wuliwuliii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值