POJ 1947 Rebuilding Roads (树形dp + 01背包)

题目链接:

http://poj.org/problem?id=1947

题解:

开一个dp数组,dp[i][j]表示以第i个节点为根,保留j个点所需要的刀数。可以先将所有dp[i][1]初始化为 i的儿子数(仅保留这个点需要的刀数),其他位置初始化为无限大。然后将问题看做一个01背包,物品的容量就是m,此时和普通的01背包区别为背包里的物品(也就是子树)可以选择k个节点砍掉或者不砍(j + k <= m)。所以d[i][j] = d[i][j-k]+d[son[i]][k]-1;(两子树拆开需要一刀)。

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

int dp[200][200], father[200];
vector <int> fson[200];
int n,m;

void init()
{
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
            dp[i][j] = 1000000;
    }
}

int dfs(int root)
{
    for(int i = 0; i < fson[root].size(); i++)
        dfs(fson[root][i]);
    dp[root][1] = fson[root].size();//只要一个点时有多少个儿子砍多少刀
    for(int i = 0; i < fson[root].size(); i++)
        for(int j = m - 1; j > 0; j--)//这里j需要从大往小和01背包类似, m为背包容量,dp记载花费,k代表我要拿的物品子树内的点,j代表我要拿的物品子树外的点
            if(dp[root][j] != 1000000)
                for(int k = 1; k <= m - j; k++)
                    if(dp[fson[root][i]][k] != 1000000)
                        dp[root][j + k] = min(dp[root][j] + dp[fson[root][i]][k]-1,dp[root][j + k]);//我砍了物品子树内的点和物品子树外的点所需要的最小花费
    return dp[root][m];
}
int main()
{
    while(cin >> n >> m)
    {
        init();
        int n1,n2,cnt = 0;
        for(int i= 0; i < n - 1; i++)
        {
            cin >> n1 >> n2;
            father[n2] = n1;
            fson[n1].push_back(n2);
        }
        int root;
        for(int i = 1; i <= n; i++)
            if(!father[i])
                root = i;
        int minn = dfs(root);
        for(int i = 1; i <= n; i++)
        {
            if(i != root && minn > dp[i][m])
                minn = dp[i][m] + 1; //之所以加1是需要将以i为根的子树与整棵树分离
        }
        cout << minn <<endl;
        memset(father,0,sizeof(father));
        memset(dp,0,sizeof(dp));
        for(int i = 0;i <= n; i++)
            fson[i].clear();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值