1. 程式人生 > >POJ - 1947 - Rebuilding Roads(樹形dp )

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;
}