树形dp(poj 1947 Rebuilding Roads )

题意:
有n个点组成一棵树,问至少要删除多少条边才能获得一棵有p个结点的子树?

思路:
设dp[i][k]为以i为根,生成节点数为k的子树,所需剪掉的边数。
dp[i][1] = total(i.son) + 1,即剪掉与所有儿子(total(i.son))的边,还要剪掉与其父亲(+1)的边。
dp[i][k] = min(dp[i][k],dp[i][j - k] + dp[i.son][k] - 2),即由i.son生成一个节点数为k的子树,再由i生成其他j-k个节点数的子树。
这里要还原i与i.son和其父亲的边所以-2。

Description

The cows have reconstructed Farmer John’s farm, with its N barns (1 <= N <= 150, number 1..N) after the terrible earthquake last May. The cows didn’t have time to rebuild any extra roads, so now there is exactly one way to get from any given barn to any other barn. Thus, the farm transportation system can be represented as a tree.

Farmer John wants to know how much damage another earthquake could do. He wants to know the minimum number of roads whose destruction would isolate a subtree of exactly P (1 <= P <= N) barns from the rest of the barns.
Input

  • Line 1: Two integers, N and P

  • Lines 2..N: N-1 lines, each with two integers I and J. Node I is node J’s parent in the tree of roads.
    Output

A single line containing the integer that is the minimum number of roads that need to be destroyed for a subtree of P nodes to be isolated.
Sample Input

11 6
1 2
1 3
1 4
1 5
2 6
2 7
2 8
4 9
4 10
4 11
Sample Output

2


#include<map>
#include<set>
#include<queue>
#include<stack>
#include<vector>
#include<math.h>
#include<cstdio>
#include<sstream>
#include<numeric>//STL数值算法头文件
#include<stdlib.h>
#include <ctype.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<functional>//模板类头文件
using namespace std;

typedef long long ll;
const int maxn=6005;
const int INF=0x3f3f3f3f;

int n,p,root;
int dp[155][155];
int father[155],son[155],brother[155];

void dfs(int root)
{
    int i,j,k,tem;
    for(i = 0; i<=p; i++)
        dp[root][i] = 10000000;
    dp[root][1] = 0;
    k = son[root];
    while(k)
    {
        dfs(k);
        for(i = p; i>=1; i--)
        {
            tem = dp[root][i]+1;
            for(j = 1; j<i; j++)
                tem = min(tem,dp[k][i-j]+dp[root][j]);
            dp[root][i] = tem;
        }
        k = brother[k];
    }
}

int solve()
{
    int ans,i;
    dfs(root);
    ans = dp[root][p];
    for(i = 1; i<=n; i++)//除了根节点,其他节点要想成为独立的根,必先与父节点断绝关系,所以要先加1
        ans = min(ans,dp[i][p]+1);
    return ans;
}

int main()
{
    int i,x,y;
    while(~scanf("%d%d",&n,&p))
    {
        memset(father,0,sizeof(father));
        memset(son,0,sizeof(son));
        for(i = 1; i<n; i++)
        {
            scanf("%d%d",&x,&y);
            father[y] = 1;//记录该点有父亲节点
            brother[y] = son[x];//记录兄弟节点
            son[x] = y;//记录子节点
        }
        for(i = 1; i<=n; i++)
        {
            if(!father[i])//找到根节点
            {
                root = i;
                break;
            }
        }
        printf("%d\n",solve());
    }

    return 0;
}





#include<map>
#include<set>
#include<queue>
#include<stack>
#include<vector>
#include<math.h>
#include<cstdio>
#include<sstream>
#include<numeric>//STL数值算法头文件
#include<stdlib.h>
#include <ctype.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<functional>//模板类头文件
using namespace std;

//typedef long long ll;
//const int maxn=6005;
//const int INF=0x3f3f3f3f;

const int MAX = 10000;

vector <int> tree[160];
//设dp[i][k]为以i为根,生成 节点数 为k的子树,所需剪掉的边数
int a, b, n, p, dp[160][160];
bool son[160];

void dfs(int root)
{
    int len=tree[root].size();
    for (int i = 0; i < len; i++)
    {
        dfs(tree[root][i]);//递归调用孩子结点(后根遍历)
        for (int j = p; j > 1; j--)//j==1 的情况已经存在 >1 即可
            for (int k = 1; k < j; k++)
                dp[root][j] = min(dp[root][j], dp[root][j - k] + dp[tree[root][i]][k] - 2);
    }
}

int main()
{
    scanf("%d %d",&n,&p);
    memset(son, false, sizeof(son));
    for (int i = 0; i < n - 1; i++)
    {
        scanf("%d %d",&a,&b);
        tree[a].push_back(b);
        son[b] = true;//记录b是否有儿子
    }
    int root = 1;
    while(son[root])//找父结点
        root++;
    for (int i = 1; i <= n; i++)
    {
        //为以i为根,生成节点数为1的子树所需剪掉的边数  每个结点都有个父结点 +1  根结点有个虚拟的父结点,方便统一处理
        dp[i][1] = tree[i].size() + 1;
        for (int j = 2; j <= p; j++)
            dp[i][j] = MAX;
    }
    dfs(root);
    dp[root][p]--;// 与dp方程中+2有关,还原i与其父亲的边,最后i为父节点,则-1
    int ans = MAX;
    for (int i = 1; i <= n; i++)
        ans = min(ans, dp[i][p]);
    printf("%d\n",ans);
    return 0;
}

再加一种写法

分析:
首先可以明确是一个树形dp题目,状态也很好定义:
dp【root】【j】:以root为根节点的子树,得到 j 个节点的子树需要最少减掉的边数,注意子树中必须保留root节点。否则无法dp
那么很明显的边界条件dp【root】【1】 = num(儿子的个数),因为要只剩一个节点的子树,那么所有的孩子都减掉,这样就为儿子的个数。
那么状态转移方程呢
dp【root】【i】 = min(dp【root】【i-k】+dp【child】【k】 - 1,dp【root】【i】);
其实就是要得到一个i个节点的子树,枚举所有的孩子为k个节点的,当前root保留 i-k 个节点,然后把root和child之间之前被剪断的连接起来,所以这里要减1
注意一些边界条件就OK了

#include<map>
#include<set>
#include<queue>
#include<stack>
#include<vector>
#include<math.h>
#include<cstdio>
#include<sstream>
#include<numeric>//STL数值算法头文件
#include<stdlib.h>
#include <ctype.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<functional>//模板类头文件
using namespace std;

typedef long long ll;
const int maxn=500;
const int INF=0x3f3f3f3f;

int dp[maxn][maxn];
vector<int> v[maxn];
int num[maxn];
int sum[maxn];  //i为根的树中所有孩子的数目,包括本身

void dfs(int root)
{
    sum[root] = 1;//注意这里
    if(v[root].size()==0)
    {
        dp[root][1] = 0;
        sum[root] = 1;
        return ;
    }
    for(int i=0; i<v[root].size(); i++)
    {
        int child = v[root][i];
        dfs(child);
        sum[root]+=sum[child];
        for(int j = sum[root]; j>0; j--)
        {
            for(int k = 1; k < j ; k++ )
            {
                dp[root][j] = min(dp[root][j-k]+dp[child][k]-1,dp[root][j]);
            }
        }
    }
}

int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        memset(num,0,sizeof(num));
        memset(sum,0,sizeof(sum));
        memset(dp,INF,sizeof(dp));
        for(int i=1; i<n; i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            v[x].push_back(y);
            num[x]++;
        }
        for(int i=1; i<=n; i++)
            dp[i][1] = num[i];
        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);
        for(int i=0; i<=n; i++)
            v[i].clear();
    }
    return 0;
}



//int num[maxn];
//int sum[maxn];  //i为根的树中所有孩子的数目,包括本身
//int dp[maxn][maxn];//dp[root][j],以root为根得到j个节点的子树最少减掉的边数
//vector<int> v[maxn];
//
//void dfs(int root)
//{
//    sum[root] = 1;//注意这里
//    if(v[root].size()==0)
//    {
//        dp[root][1] = 0;
//        sum[root] = 1;
//        return ;
//    }
//    for(int i=0; i<v[root].size(); i++)
//    {
//        int child = v[root][i];
//        dfs(child);
//        sum[root]+=sum[child];
//        for(int j = sum[root]; j>0; j--)
//        {
//            for(int k = 1; k < j ; k++ )
//            {
//                dp[root][j] = min(dp[root][j-k]+dp[child][k]-1,dp[root][j]);
//            }
//        }
//    }
//}
//
//int main()
//{
//    int n,m;
//    while(~scanf("%d%d",&n,&m))
//    {
//        memset(num,0,sizeof(num));
//        memset(sum,0,sizeof(sum));
//        memset(dp,INF,sizeof(dp));
//        for(int i=1; i<n; i++)
//        {
//            int x,y;
//            scanf("%d%d",&x,&y);
//            v[x].push_back(y);
//            num[x]++;
//        }
//        for(int i=1; i<=n; i++)
//            dp[i][1] = num[i];
//        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);
//        for(int i=0; i<=n; i++)
//            v[i].clear();
//    }
//    return 0;
//}
posted @ 2017-05-07 19:04  xushukui  阅读(238)  评论(0编辑  收藏  举报