基環樹
基環樹就是類似於在樹上加了一條邊形成了環,去點環上的一條邊後就會變成數,如下圖。
這是一個 \(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;
}