【演算法學習】基環樹

sad_lin發表於2024-10-29

基環樹

基環樹就是類似於在樹上加了一條邊形成了環,去點環上的一條邊後就會變成數,如下圖。

image

這是一個 \(n\) 個點 \(n\) 條邊的連通圖,如果不保證聯通,它就會成為基環樹森林。

外向樹:每個點都只有一條入邊,因為向內上。

內向樹:每個點都只有一條出邊,因為向外少。

怎麼用呢?

因為有環的性質,所以可以將環拆成樹,再對樹繼續樹形dp即可。

例題

P2607 [ZJOI2008] 騎士

因為一個騎士只會討厭另外一個騎士,所以每個連通塊都是一顆基環樹,所以我們對每顆基環樹拆環成樹進行樹形dp即可。

我們可以把圖建成一個有向圖,他討厭的騎士作他的父親,因為我們環上相鄰的兩個點都不能選,所以我們先強制不選根節點進行一遍樹形dp,狀態為f[x][1/0]表示選/不選x節點最大的權值,然後再對根節點的父親進行一遍樹形dp,這樣就包含了所有情況了!!

#include <bits/stdc++.h>
#define re register 
const int N=1e6+1e5;
#define int long long
const int inf=1e9;
using namespace std;
int n;
vector<int> v[N];
int vis[N];
int f[N][3];
int fa[N];
int a[N];
int ans=0;
int mx=0;
int root=1;

void dfs(int x){
	vis[x]=1;
	f[x][0]=0;
	f[x][1]=a[x];
	for(int i:v[x]){
		if(i!=root){
			dfs(i);
			f[x][0]+=max(f[i][1],f[i][0]);
			f[x][1]+=f[i][0];
		}
		else{
			f[i][1]=-inf;
		}
	}
} 

signed main(){
    ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i]>>fa[i];
		v[fa[i]].push_back(i);
	}
	for(int i=1;i<=n;i++){
		if(!vis[i]){
			vis[i]=1;
			mx=0;
			root=i;
			while(!vis[fa[root]]){
				vis[root]=1;
				root=fa[root];
			}
			vis[root]=1;
			dfs(root);
			mx=max(f[root][1],f[root][0]);
			root=fa[root];
			dfs(root);
			ans+=max(mx,max(f[root][1],f[root][0]));
		}
	}
	cout<<ans;
    return 0;
}

相關文章