POJ - 1947 - Rebuilding Roads(树形dp )

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
    Hint
    [A subtree with nodes (1, 2, 3, 6, 7, 8) will become isolated if roads 1-4 and 1-5 are destroyed.]
    题目链接
    参考题解

  • 题意:给你一棵有n个节点的树,从中取出一个有p个节点的子树,那么最少减掉多少边。
  • 思路:树形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了 (这里说一下,一定要理解我们dp的含义,这是记录的自身和子节点以及子节点的子节点形成的子树,和父节点以及祖先什么的没有关系,所以这里要减一,加上一条与root 和 child之间的边)
    因为找子树,仅仅是这样dfs一遍然后看dp[1][i]的话,就不对了,因为这样只是看了以1为根节点的子树,所以还应该把所有的点都遍历一遍。而且一定要注意还要再此基础上加一,也就是多砍掉一条边。为什么呢,(这个地方和上面的-1我都想了很久,笔者太笨,读者应该不会),这里我们注意,我们dp处理的事以这个节点为祖先的子树,也就是不涉及这个节点的父亲和祖先,但是有一点不一样啊,1是整颗树的祖先,所以只需要关心它的孩子就行了,但是别的点都是有父节点的,那么我们只处理孩子节点显然是不合理的,他还连着一条边通往父节点是我们没处理的,所以,我们还要把这一条砍断,所以就有了+1的操作。(如果想象不出来,就画图,一定要动手,画出图来自己举个栗子简单跑一边代码就恍然大悟)
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 200;
const int inf = 0x3f3f3f3f;
vector<int> son[maxn];
//dp[i][j] is to record the number of cutting edges to get the subtree which has j nodes when i was a root
//vis is to record the number of its son, sum is the number of all sons of the subtree
int dp[maxn][maxn], vis[maxn], sum[maxn];

void dfs(int root)
{
    sum[root] = 1;  //sum equal to 1, including itself
    if(son[root].size() == 0)   //this is the leaf of the tree, so it has no son
    {
        dp[root][1] = 0;    //it has no subtree regard itself as root, so it needn't cut any edge
        sum[root] = 1;  //including itself, it has only one son
        return ;
    }
    for(int i = 0; i < son[root].size(); i++)   //loop to judge all its sons and get the best condition of dp
    {
        int child = son[root][i];
        dfs(child);
        sum[root] += sum[child];
        for(int j = sum[root]; j > 0; j--)  //get dp of all kinds of subtree taking this node as root
            for(int s = 1; s < j ; s++) //compare with all kinds of subtrees taking its son as root
                dp[root][j] = min(dp[root][j - s] + dp[child][s] - 1, dp[root][j]);
                //dp only include itself and all its sons, but didn't contact with its father, so we should build a edge between them
    }
}

int main()
{
    int n, m;
    while(~scanf("%d%d", &n, &m))
    {
        //init
        memset(vis, 0, sizeof(vis));
        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);
            son[x].push_back(y);
            vis[x]++;   //the number of its sons x's added
        }
        //if the subtree has the only son, itself, and the number of edge should be cut is the number of its sons
        for(int i = 1; i <= n; i++)
            dp[i][1] = vis[i];
        dfs(1);
        int ans = dp[1][m];
        //except the subtree taking 1 as root, others should cut the route between its father ans i, so 1 should be added to dp[i][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++)
            son[i].clear();
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_40788897/article/details/84144567