1. 程式人生 > >Rebuilding Roads 【POJ

Rebuilding Roads 【POJ

題目連結

   很好的一道題,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寫自己的思考,也算是因為理解到了些東西做下的筆記,若有什麼錯誤的想法,歡迎大佬指教。