洛谷P1352 沒有上司的舞會
題目描述
某大學有 \(n\) 個職員,編號為 \(1\ldots n\)。
他們之間有從屬關係,也就是說他們的關係就像一棵以校長為根的樹,父結點就是子結點的直接上司。
現在有個週年慶宴會,宴會每邀請來一個職員都會增加一定的快樂指數 \(r_i\),但是呢,如果某個職員的直接上司來參加舞會了,那麼這個職員就無論如何也不肯來參加舞會了。
所以,請你程式設計計算,邀請哪些職員可以使快樂指數最大,求最大的快樂指數。
輸入格式
輸入的第一行是一個整數 \(n\)。
第 \(2\) 到第 \((n + 1)\) 行,每行一個整數,第 \((i+1)\) 行的整數表示 \(i\) 號職員的快樂指數 \(r_i\)
第 \((n + 2)\) 到第 \(2n\) 行,每行輸入一對整數 \(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
輸出 #1
5
說明/提示
資料規模與約定
對於 \(100\%\) 的資料,保證 \(1\leq n \leq 6 \times 10^3\),\(-128 \leq r_i\leq 127\),\(1 \leq l, k \leq n\),且給出的關係一定是一棵樹。
2020-5-4
思路
很顯然,這是一個樹形dp(我也沒學過,老師說的QWQ),那麼首先要找狀態轉移方程,每個節點有兩個需要dp的條件,一個是選或者不選,另一個是當前位置,所以開一個二維陣列來記錄資料。第一維表示狀態,第二維記錄位置。所以思路就很清晰了,詳情見程式碼。
程式碼
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<cstdlib> #include<algorithm> using namespace std; int ru[12010],po[12010],ne[12010],f[5][12010];//開陣列,ru陣列的含義是有多少個父親節點,po是父節點編號,next就是子節點,二維的f陣列則記錄最值答案 int n,a,b,root;//root是根結點 void dp(int x) { for(int i=po[x]; i; i=ne[i]) //我明白這裡了,這條邊當然要從它的起點開始迴圈,那麼迴圈一次之後就要到它的下一條邊 { dp(i); f[1][x]=max(max(f[1][x],f[1][x]+f[0][i]),f[0][i]);//其實這裡就是分類討論,討論有幾種情況,這裡分別是:1.只選當前的節點。2.選當前節點並且加上不選的子節點的最值。3.賦值不選的那個子節點位置上的最值。可能會有疑問,我這個大的分類就是選這個節點啊,為什麼還能不選這個節點,只選子節點。我也有這個疑問。。。。。。。。。。。。。。。。。。 f[0][x]=max(max(f[0][x],f[0][x]+f[1][i]),max(f[1][i],f[0][i]));//這裡多了一種情況:1.自己不選。2.自己不選選子節點。3.只選子節點。4.不選子節點。我也暈了 } } int main() { scanf("%d",&n); for(int i=1; i<=n; i++) scanf("%d",&f[1][i]); for(int i=1; i<=n; i++) { scanf("%d%d",&b,&a); ru[b]++; ne[b]=po[a]; po[a]=b; } for(int i=1; i<=n; i++) { if(ru[i]==0)//如果一個節點沒有父親節點,那肯定是根結點 { root=i;//記錄根結點的位置 break; } } dp(root);//從根結點開始dp,因為函式裡是先遞迴,再寫內容,所以會從最下面的葉子節點開始,那麼到最後資料都在根節點裡 printf("%d",max(f[1][root],f[0][root]));//同上 return 0; }
2020-8-12
先吐槽一句,三個月前的我怎麼這麼菜,而且這篇題解寫的不好,把初學樹形dp的我都繞暈了,這個狀態轉移實在是太複雜了。經過三個月的成長,我現在終於有能力切掉這道題了。
思路
其實還是差不多,開一個二維陣列f[i][j]來表示以i為根結點的子樹的最大開心度,j為0或1,代表根節點選還是不選,這個思路非常巧妙,只要想到就不難了。樹形dp一般來說都是遞迴求解。所以狀態轉移方程就是如果沒選當前根節點,那麼選子節點和不選都可以。如果選了,那麼只能不選子節點。即f[x][0] += max( f[y][1] , f[y][0] ) , f[x][1] += f[y][0]。(比三個月前的思路香多了,好理解而且程式碼很好寫)
程式碼
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<cstdlib>
#include<ctime>
using namespace std;
int n,root;
int a[6005],v[6005],f[6005][3];
vector<int> son[6005];
void dp(int x){
f[x][0]=0;
f[x][1]=a[x];//選或不選的值
for(int i=0;i<son[x].size();i++){
int y=son[x][i];
dp(y);//遍歷子樹並遞迴
f[x][0]+=max(f[y][1],f[y][0]);
f[x][1]+=f[y][0];//狀態轉移方程
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1,l,k;i<=n-1;i++){
scanf("%d%d",&l,&k);
son[k].push_back(l);
v[l]=1;//標記,便於查詢根節點
}
for(int i=1;i<=n;i++){
if(v[i]==0){
root=i;//要從根節點開始遞迴,所以要找根節點
break;
}
}
dp(root);
printf("%d\n",max(f[root][0],f[root][1]));//選根節點和不選之間選一個最大值輸出
return 0;
}