基环树
基环树就是类似于在树上加了一条边形成了环,去点环上的一条边后就会变成数,如下图。
这是一个 \(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;
}
标签:fa,int,学习,基环树,算法,树形,骑士,dp
From: https://www.cnblogs.com/sadlin/p/18514214