DP專題7 | 沒有上司的舞會 洛谷1352(樹形DP)

本篇繼續咱們的DP專題,樹形DP入門。動態規劃每一個類型的DP都是深坑,期望童鞋們自己在這個系列的基礎上多花時間進行拓展,學習愉快~

 

在討論樹形DP之前,我想介紹一個比較有名的學習技巧——費曼技巧,因爲個人覺得可以嘗試着用在咱們的算法理解上。費曼這個人本身是一個很有意思的人,做科研和教育都非常厲害,另外後人還根據他的個人經歷拍了一部愛情片,是不是跨越有點大,好了,先說費曼定理。

 

 

2012年,加拿大人斯科特.H.楊(Scott H Young)用一年的時間自學完成了MIT公開課上正常情況下需要四年才能修完的計算機科學的33門課程,並最終通過了所有考試。在分享其學習方法的時候,他提到了費曼技巧,從此,費曼技巧進入了公衆視野。

 

那麼,費曼技巧到底是什麼呢?

 

在費曼的自傳裏,他提到曾糾結於某篇艱深的研究論文。他的辦法是,仔細審閱這篇論文的輔助材料(supporting material),直到他掌握了相關的知識基礎、足以理解其中的艱深想法爲止。

 

費曼技巧,也可以稱爲四步學習法:

 

第一步 選擇一個你想要理解的概念

第二步 設想一種場景,你正要向別人傳授這個概念

第三步 如果你感覺卡殼了, 就回顧一下學習資料

第四步 爲了讓你的講解通俗易懂,簡化語言表達

 

最終的目的, 是用你自己的語言, 而不是學習資料中的語言來解釋概念。

 

先看到這裏,下週會專門發一篇費曼技巧的文章。接着來看樹形DP題目。

 

現學現用,思考一下樹形DP的費曼解釋

假設你要對一個是7歲小學1年級學生講解,要怎樣解釋樹形DP呢?

 

首先假設這個小學生已經瞭解了DP(1年級能理解DP也是厲害了),那麼可以說:

 

樹形DP就是基於樹形搜索進行狀態轉移的DP。

好吧,還是很有難度的,有興趣的童鞋可以留言說出你的想法

因爲樹形天生具有遞歸的性質,樹形DP一般都是通過深度優先搜索進行迭代,從葉子節點回溯到根節點的過程。

 

你可以想象自己在一棵倒掛樹的葉子節點上,一點點往根部爬。之前線性DP我們都是從f[0]這樣的位置一點點往f[n]這樣遞推,樹形DP是換了一種方式,從線性數組到樹形。

 

題目描述

某大學有N個職員,編號爲1~N。他們之間有從屬關係,也就是說他們的關係就像一棵以校長爲根的樹,父結點就是子結點的直接上司。現在有個週年慶宴會,宴會每邀請來一個職員都會增加一定的快樂指數Ri,但是呢,如果某個職員的上司來參加舞會了,那麼這個職員就無論如何也不肯來參加舞會了。所以,請你編程計算,邀請哪些職員可以使快樂指數最大,求最大的快樂指數。

輸入輸出格式

輸入格式:

第一行一個整數N。(1<=N<=6000)

接下來N行,第i+1行表示i號職員的快樂指數Ri。(-128<=Ri<=127)

接下來N-1行,每行輸入一對整數L,K。表示K是L的直接上司。

最後一行輸入0 0

輸出格式:

輸出最大的快樂指數。

本題是樹形DP的經典入門題目,因爲理解起來沒那麼複雜,轉移方程如下圖所示:

只用考慮上司去和不去2中狀態,進行狀態轉移。

源代碼:G++編譯

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <bitset>
#include <iostream>
#include <set>
#include <string>
#include <vector>
#include <string.h>
using namespace std;

#define N 6100

int h[N];
int v[N];
vector<int> children[N];
int dp[N][2];

void dfs(int x)
{
    // 對於葉子節點來說
    // 不去爲0分
    dp[x][0] = 0;
    // 去的話初始化爲自己的歡樂指數
    dp[x][1] = h[x];

    for(int i=0; i<children[x].size(); ++i){
        int y = children[x][i];
        // 先遞歸計算孩子的歡樂指數
        dfs(y);
        // 加上孩子的歡樂指數
        // 上司不去,孩子有可能去
        dp[x][0] += max(dp[y][0], dp[y][1]);
        // 上司去,孩子不去
        dp[x][1] += dp[y][0];
    }
}

int main() {
#ifdef __MSYS__
    freopen("test.txt", "r", stdin);
#endif

    int n;
    scanf("%d", &n);

    // 注意下標從1開始,因爲題目都是用下標來指定位置的
    for(int i=1; i<=n; ++i){
        scanf("%d", &h[i]);
    }

    for(int i=1; ; ++i){
        int c, p;
        scanf("%d%d", &c, &p);
        if(!c && !p)break;
        // 用於判斷是否爲根節點,只有根節點沒有parent
        v[c] = 1;
        // 用二維數組記錄父子關係
        children[p].push_back(c);
    }

    int root;
    for(int i=1; i<=n; ++i){
        if(!v[i]){
            root = i;
            break;
        }
    }
    // 開始樹形遞歸搜索(線性DP都是迭代搜索)
    dfs(root);
    printf("%d\n", max(dp[root][0], dp[root][1]));
    return 0;
}

下期預告:DP專題 8 | 狀態壓縮DP

 

往期回顧

DP專題 6 | 石子合併 CH5301(區間DP)

DP專題 5 | 顏色的長度 - UVA1625(線性DP)

DP專題 4 | 骨頭收集愛好者 - POJ 1458( 0-1揹包)

DP專題 3 | LCS最長公共子序列 - POJ 1458

DP專題 2 | LIS POJ - 2533(經典DP)

DP專題 1 | 數字三角形 - POJ 1163(簡單DP)

前綴和、二維前綴和與差分的小總結

 

個人公衆號(acm-clan):ACM算法日常

專注於基礎算法的研究工作,深入解析ACM算法題,五分鐘閱讀,輕鬆理解每一行源代碼。內容涉及算法、C/C++、機器學習等。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章