1. 程式人生 > >樹形背包

樹形背包

遊戲 bool const 獲得 討論 2個 表示 input mes

原題為HDU1561 The more,The better

Problem Description ACboy很喜歡玩一種戰略遊戲,在一個地圖上,有N座城堡,每座城堡都有一定的寶物,在每次遊戲中ACboy允許攻克M個城堡並獲得裏面的寶物。但由於地理位置原因,有些城堡不能直接攻克,要攻克這些城堡必須先攻克其他某一個特定的城堡。你能幫ACboy算出要獲得盡量多的寶物應該攻克哪M個城堡嗎? Input 每個測試實例首先包括2個整數,N,M.(1 <= M <= N <= 200);在接下來的N行裏,每行包括2個整數,a,b. 在第 i 行,a 代表要攻克第 i 個城堡必須先攻克第 a 個城堡,如果 a = 0 則代表可以直接攻克第 i 個城堡。b 代表第 i 個城堡的寶物數量, b >= 0。當N = 0, M = 0輸入結束。 Output
對於每個測試實例,輸出一個整數,代表ACboy攻克M個城堡所獲得的最多寶物的數量。 Sample Input 3 2 0 1 0 2 0 3 7 4 2 2 0 1 0 4 2 1 7 1 7 6 2 2 0 0 Sample Output 5 13 這道題有兩種算法,第一種算法是O(nw^2)的,就是直接在樹上dp,之後枚舉每一個節點,枚舉每一個節點取得最大體積和當前取得的體積,就可以。 不過我們今天來說一下O(nw)的算法,因為普通的背包是可以線性解題的,這樣才能達到二維的時間復雜度,那麽我們怎麽把一棵樹轉化為線性問題求解呢? 我們可以先求這棵樹的dfs序,之後記錄每一個點的子樹大小,之後像正常dp一樣討論取或不取即可。
if
(j+1<=m) dp[i+1][j+1] = max(dp[i+1][j+1],dp[i][j]+w[seq[i]]); dp[i+size[seq[i]]][j] = max(dp[i+size[seq[i]]][j],dp[i][j]);

第一行是取,第二行是不取,其中dp[i][j]表示在dfs序中到第i個節點已經用了j個體積所能獲得的最大價值。因為這道題攻陷每個城堡所用的消耗固定為1,所以直接+1就可以。

不過這題並不是直接照抄dp轉移方程就可以!這裏有好多需要註意的事情。

第一個,我們在樹形dp轉化為線性問題的時候,要註意一個狀態是不可以憑空生成的,也就是說我們不能在沒有取父節點的情況下直接選取子節點,這會對第一行代碼產生極大的影響,所以我們開一個vis數組以記錄這種狀態是否被取過,如果被取過的話我們就可以繼續dp,否則不可以。

第二個,註意子樹大小是怎麽計算的……他是算自己的。所以我們遞歸的時候應該先把每個節點初始化為1,之後(我用的是鏈前)直接把其所有子節點的子樹大小加起來就可以了。

第三個是dfs序的問題,這裏一定要註意i表示dfs序中到第i個點,所以dfs序從0還是1開始存將會有至關重要的影響。在代碼裏我是從1開始存並從1開始dp,所以最後我們只要去dp[tot][m]就可以,其他情況需要視情況而定,如果實在不行的話自己打表分析一下。

這樣就可以過了……要註意的東西挺多的,而且HDU不提供數據點debug很難受……

最後上代碼。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<queue>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar(‘\n‘)
#define M 2005
using namespace std;
typedef long long ll;
const int INF = 2147483647;
struct node
{
    int next,to;
}edge[M];
int n,dp[M][M],head[M],size[M],cnt,tot,ans,m,seq[M],w[M];
bool vis1[M][M];
int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < 0 || ch > 9)
    {
        if(ch == -) op = -1;
        ch = getchar();
    }
    while(ch >= 0 && ch <= 9)
    {
        ans *= 10;
        ans += ch - 0;
        ch = getchar();
    }
    return ans * op;
}
void init()
{
    memset(edge,0,sizeof(edge));
    memset(head,0,sizeof(head));
    memset(dp,0,sizeof(dp));
    memset(size,0,sizeof(size));
    memset(seq,0,sizeof(seq));
    memset(w,0,sizeof(w));
    memset(vis1, 0, sizeof(vis1));
    cnt = 0,tot = 0;
}
void add(int x,int y)
{
    edge[++cnt].next = head[x];
    edge[cnt].to = y;
    head[x] = cnt;
}
void build()
{
    rep(i,1,n)
    {
        int x = read();
        int y = read();
        add(x,i);
        w[i] = y;
    }
}
void dfs(int x)
{
    seq[tot++] = x; size[x] = 1;
    for(int i = head[x];i;i = edge[i].next)
    {
        int v = edge[i].to;
        dfs(v);
        size[x] += size[v];
    }
} 
void DP()
{
    vis1[1][0] = 1;
    rep(i,1,tot + 1)
    rep(j,0,n)
    {
        if(vis1[i][j])
        {
            if(j+1<=m) dp[i+1][j+1] = max(dp[i+1][j+1],dp[i][j]+w[seq[i]]);
            dp[i+size[seq[i]]][j] = max(dp[i+size[seq[i]]][j],dp[i][j]);
            vis1[i+1][j+1] = vis1[i+size[seq[i]]][j] = 1;
        }
    }
}
int main()
{
    while(1)
    {
        n = read(),m = read();
        if(!n && !m) break;
        init();
        build();
        dfs(0);
        DP();
        printf("%d\n",dp[tot][m]);
    }
    return 0;
}

樹形背包