[筆記]樹形dp

Sinktank發表於2024-05-04

樹形dp,是一種建立在樹形結構上的dp,因此dfs一般是實現它的通用手段。
是一種很美的動態規劃呢。

P1352 沒有上司的舞會

P1352 沒有上司的舞會

在一棵樹中,找到若干個互相獨立(即互相沒有邊直接相連)的點,使它們的權值和最大。

我們發現,間隔選擇的方法(只選深度為奇數/偶數的點)是不可行的。一個很簡單的反例是這棵樹是一條鏈:10 <-> 3 <-> 3 <-> 10,顯然選擇\(1,4\)才是正確的。

那麼我們該怎麼做呢?我們可以dfs遍歷這棵樹,對於一個節點,我們考慮兩種選擇情況:

  • 選當前節點:那麼子節點就不能選。當前權值為所有子節點不選狀態下的答案和。
  • 不選當前節點:那麼子節點可以選,也可以不選。當前答案為所有子節點兩種狀態下的最大值之和。
點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
int n,happy[6010];
vector<int> ch[6010];
bool b[6010];
int mem[6010][2];
int dfs(int pos,bool is){
	if(mem[pos][is]) return mem[pos][is];
	int ans=0,len=ch[pos].size();
	if(is){//如果上司去,則員工必須不去
		for(int i=0;i<len;i++) ans+=dfs(ch[pos][i],0);
		ans+=happy[pos]*(happy[pos]>0);
	}else{//如果上司不去,則員工有兩種選擇
		for(int i=0;i<len;i++) ans+=max(dfs(ch[pos][i],0),dfs(ch[pos][i],1));
	}
	return mem[pos][is]=ans;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>happy[i];
	for(int i=1;i<n;i++){
		int x,y;
		cin>>x>>y;
		b[x]=1;
		ch[y].push_back(x);
	}
	for(int i=1;i<=n;i++){
		if(!b[i]){
			cout<<max(dfs(i,0),dfs(i,1));
			break;
		}
	}
	return 0;
}

UVA1292 Strategic game

UVA1292 Strategic game

在一棵樹中,找到若干個點,每個點放置\(1\)個士兵,每個士兵可以看守所在點鄰接的所有邊,現在我們想知道:要看守這棵樹的所有邊,最少需要多少士兵。

對於每個節點,考慮其選擇情況:

  • 該節點放士兵:那麼子節點可以放,也可以不放。取每個子節點放/不放的最小值求和即可。
  • 該節點不放士兵:那麼子節點必須全放。取每個子節點放的和即可。
點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
int n;
vector<int> G[1510];
int f[1510][2];
bool vis[1510];
void dfs(int pos){
	f[pos][1]=1;
	for(auto i:G[pos]){
		dfs(i);
		f[pos][0]+=f[i][1];//如果當前不選,那麼子節點必須全選
		f[pos][1]+=min(f[i][0],f[i][1]);//如果當前選,則選最小
	}
}
int main(){
	while(~scanf("%d",&n)){
		memset(f,0,sizeof f);
		memset(vis,0,sizeof vis);
		for(int i=1;i<=n;i++) G[i].clear();
		for(int i=1;i<=n;i++){
			int pos,m,num;
			scanf("%d:(%d)",&pos,&m);
			pos++;
			for(int j=1;j<=m;j++){
				cin>>num;
				num++;
				G[pos].push_back(num);
				vis[num]=1;
			}
		}
		int awa=-1;
		for(int i=1;i<=n;i++){
			if(!vis[i]){
				awa=i;
				break;
			}
		}
		dfs(awa);
		cout<<min(f[awa][0],f[awa][1])<<endl;
	}
	return 0;
}

P1122 最大子樹和

P1122 最大子樹和

在一棵樹中選擇一塊聯通分量,讓其點權和最大。

\(f[i]\)表示以\(i\)為根節點子樹的答案。顯然它的值就是子節點答案中,非負答案的和。

最後遍歷\(f\)求出最大值即可。

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
int n,v[16010];
vector<int> G[16010];
bool vis[16010];
int f[16010];
void dfs(int pos){
	vis[pos]=1;
	f[pos]=v[pos];
	for(auto i:G[pos]){
		if(vis[i]) continue;
		dfs(i);
		if(f[i]>0) f[pos]+=f[i];
	}
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>v[i];
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(1);
	int ans=INT_MIN;
	for(int i=1;i<=n;i++) ans=max(ans,f[i]);
	cout<<ans;
	return 0;
}

相關文章