樹形動態規劃(最大子獨立集)
阿新 • • 發佈:2019-02-04
最大子獨立集
對於一棵有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]));
}