1. 程式人生 > 實用技巧 >洛谷P1352 沒有上司的舞會

洛谷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;
}