1. 程式人生 > >樹形動態規劃(最大子獨立集)

樹形動態規劃(最大子獨立集)

最大子獨立集

對於一棵有N個結點的無根樹,選出儘量多的結點,使得任何兩個結點均不相鄰(稱為最大獨立集)。

輸入

 第1行:1個整數N(1 <= N <= 6000),表示樹的結點個數,樹中結點的編號從1..N
接下來N-1行,每行2個整數u,v,表示樹中的一條邊連線結點u和v

輸出

 第1行:1個整數,表示最大獨立集的結點個數

樣例輸入

11
1 2
1 3
3 4
3 5
3 6
4 7
4 8
5 9
5 10
6 11

樣例輸出

7

分析

這一題是學習樹形DP的基礎,要求掌握,題目本身不難,只是樹形DP入門。
由題意可知,當我們選擇一個點時,就不能選擇與它相鄰的。所以這個點分為選與不選兩種狀態。
所以定義狀態Dp[i][1](選擇i點的最優解),Dp[i][0](不選i點的最優解)。
最後的解就是Dp[root][1]與Dp[root][0]中大的那個,root是根節點。
可能會問:題目不是說無根樹嗎?
對,這裡是無根樹,但由於演算法需求,我們先轉成有根數,即隨便找一個點當根(此後,就一直以它為根)。
在學習樹形Dp的階段,會遇到很多需要無根轉有根的。
廢話就不多說了,接下來看狀態轉移方程。
想一想,Dp[i][1]能從什麼狀態轉過來,
這很好想,既然i這個點選了,那它兒子就不選呀。
所以Dp[i][1]+=Dp[v][0]; 其中,v是兒子編號,至於‘+=’嘛,因為答案是叫求和。
那Dp[i][0]呢?
i選了,它兒子必須選嗎?
所以Dp[i][0]+=max(Dp[v][0],Dp[v][1]);

下面給出完整程式碼(註釋)
差點忘了,還有輸入
題目只是說兩邊相連,但並沒有說誰是父親。
所以我們用一個動態陣列存點的兒子
所以輸入點x,y時
就把x的兒子存成y
y的兒子存成x
在求解時,函式傳參時維護一個父親,
只需判斷兒子是不是父親即可。
下面是完整程式碼

Code

#include<cstdio>
#include<vector>
#include<iostream>
using namespace std;
int n;
int Dp[6005][2];//狀態
vector<int>G[6005];//兒子 
void Tdp(int
x,int fa){ Dp[x][1]=1;//因為每個點都算一個,即使是葉節點 for(int i=0;i<G[x].size();i++){//列舉兒子 int v=G[x][i];//簡化程式 if(v==fa)continue;//兒子等於父親 Tdp(v,x);//遞迴求解,先計算下層的節點(想想為什麼) Dp[x][1]+=Dp[v][0]; Dp[x][0]+=max(Dp[v][0],Dp[v][1]);//轉移 } } int main(){ scanf("%d",&n); for
(int i=1;i<n;i++){ int x,y; scanf("%d%d",&x,&y); G[x].push_back(y); G[y].push_back(x);//存入陣列 } Tdp(1,0);//把1號當成根,由於題目中沒有0號節點,所以父親為0(最好為-1) printf("%d\n",max(Dp[1][0],Dp[1][1]));//最優解 }

要利用好基礎解決更多問題
接下來看一道複雜一點的題

題目描述

Ural大學有N個職員,編號為1~N。他們有從屬關係,也就是說他們的關係就像一棵以校長為根的樹,父結點就是子結點的直接上司。每個職員有一個快樂指數。現在有個週年慶宴會,要求與會職員的快樂指數最大。但是,沒有職員願和直接上司一起參加宴會。

輸入

第一行一個整數N。(1≤N≤6000)
接下來N行,第i+1行表示i號職員的快樂指數Ri。(-128≤Ri≤127)
接下來N-1行,每行輸入一對整數L,K。表示K是L的直接上司。

輸出

第1行:輸出最大的快樂指數。

樣例輸入

7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5

樣例輸出

5

思路基本一樣,下面給出程式碼

#include<cstdio>
#include<vector>
using namespace std;
int n,rt;
int Dp[6005][2],hap[6005];//hap為開心值
vector<int>G[6005];//兒子
int F[6005];//存父親
void Tdp(int x){
    Dp[x][1]=hap[x];//不知你有沒有發現,之前的1變成了各自的開心值
    for(int i=0;i<G[x].size();i++){//由於知道根,關係固定,所以不用判斷,
        int v=G[x][i];
        Tdp(v);
        Dp[x][1]+=Dp[v][0];
        Dp[x][0]+=max(Dp[v][0],Dp[v][1]);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&hap[i]);
    for(int i=1;i<n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        F[x]=y;//存父親
        G[y].push_back(x);
    }
    for(int i=1;i<=n;i++)//多了一個找根的過程
        if(!F[i]){rt=i;break;}
    Tdp(rt);
    printf("%d\n",max(Dp[rt][0],Dp[rt][1]));
}

有時候,這種題會結合其它知識,看一道吧

題目描述

你要組織一個由你公司的人蔘加的聚會。你希望聚會非常愉快,儘可能多地找些有趣的熱鬧。但是勸你不要同時邀請某個人和他的上司,因為這可能帶來爭吵。
給定N個人(姓名,他參加聚會的歡樂指數,以及他上司的名字),程式設計找到能使歡樂指數之和最大的若干個人。

輸入

第一行一個整數N(N<100)。
接下來有N行,每一行描述一個人的資訊,資訊之間用空格隔開。姓名是長度不超過20的字串,歡樂指數是在0到100之間的整數。

輸出

第1行:所邀請的人最大的歡樂指數之和

樣例輸入

5                               
BART 1 HOMER
HOMER 2 MONTGOMERY
MONTGOMERY 1 NOBODY
LISA 3 HOMER
SMITHERS 4 MONTGOMERY

樣例輸出

8

是不是很熟悉,但這一題存在一個問題,讀入不在是編號,而是字串,這該怎麼辦呢?
如果學過的同學應該會想到,如何把字串和編號聯絡起來。
對,用map(關聯式容器)又叫對映
在程式碼中解釋

#include<cstdio>
#include<iostream>
#include<vector>
#include<map>
using namespace std;
#define MAXN 100
int n,rt;
int Dp[MAXN+5][2],H[MAXN+5],F[MAXN+5];//H為開心值,F為父親編號
vector<int>G[MAXN+5];//兒子
map<string,int>N;//字串對應編號
map<int,string>FF;//編號對應父親的字串
void Tdp(int x){//函式一樣,主要多一個轉化
    Dp[x][1]=H[x];
    Dp[x][0]=0;
    for(int i=0;i<G[x].size();i++){
        int v=G[x][i];
        Tdp(v);
        Dp[x][1]+=Dp[v][0];
        Dp[x][0]+=max(Dp[v][0],Dp[v][1]);
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        string x,y; int z;
        cin>>x;scanf("%d",&z);cin>>y;//讀入
        N[x]=i; FF[i]=y; H[i]=z;
        //map就是這麼666,可以把字串、負數等當下標
    }
    for(int i=1;i<=n;i++){//開始轉化編號
        F[i]=N[FF[i]];
        G[F[i]].push_back(i);
        if(!F[i])rt=i;
    }
    Tdp(rt);
    printf("%d\n",max(Dp[rt][0],Dp[rt][1]));
}