基环树
定义
由 \(n\) 个点、\(n\) 条边组成的图。
可以理解为一棵树再加上一条边。
基环树不是树!
若干个基环树组成基环森林。
每一个基环树都有一个环,以这个环为根向外延伸出若干棵树。
无向基环树
由 \(n\) 个点、\(n\) 条边组成的无向图。

外向基环树
每一个节点的入度为 \(1\)。

优点是便于进行树上DP。
内向基环树
每一个节点的出度为 \(1\) 。

优点是便于找环。
处理
对于基环树,一般有两种处理方式:
- 将基环树视为一个环和若干个树,先计算树上的信息,在将环上的信息合并。
- 将基环树视为一棵树加一条边,先将这条边断掉进行计算,在考虑加上这一条边。
不同的题目会有不同的处理方式,选择最好写的一个就行。
例题
[ZJOI2008] 骑士\(^{louguP2607}\)
若这是一颗树,考虑树上DP。
设 \(f[x][0/1]\) 表示在以 \(x\) 为根的子树中,第 \(x\) 个点 选/不选 所获得的最大战力。
可以列出转移方程:
\[f[x][0]=\sum_{y\in son[x]}{max\{f[y][1],f[y][0]\}}
\]
\[f[x][1]=\sum_{y\in sun[x]}{f[y][0]}
\]
考虑环的问题。
我们先找到换上一边,将其中一条边为根进行树上DP,此时 \(f[x][0]\) 的值是正确的,但 \(f[x][1]\) 需要再绕环走一圈来进行更新。
使用前向星建立外向基环树,再记录每个节点的 \(fa\) 这样即有外向基环树方便DP的优点,也有内向基环树便于找环的优点。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NUM=1e6+10;
int fa[NUM],w[NUM],ans,f[NUM][2],n;
int h[NUM],nxt[NUM],to[NUM],tot;
bool vis[NUM],vis1[NUM];
void addedge(int x,int y){
to[++tot]=y;
nxt[tot]=h[x];
h[x]=tot;
fa[y]=x;
}
void dfs(int x){
vis[x]=1;
f[x][1]=w[x];
for(int i=h[x];i;i=nxt[i]){
int y=to[i];
if(vis[y]) continue;
dfs(y);
f[x][0]+=max(f[y][1],f[y][0]);
f[x][1]+=f[y][0];
}
}
void dfs1(int x){
int root;
for(root=x;vis1[root]==0;root=fa[root]) vis1[root]=1;
dfs(root);
x=fa[root];
f[x][1]=f[x][0];
for(x=fa[x];x!=root;x=fa[x]){
f[x][1]=w[x];
f[x][0]=0;
for(int j=h[x];j;j=nxt[j]){
int y=to[j];
f[x][1]+=f[y][0];
f[x][0]+=max(f[y][1],f[y][0]);
}
}
f[root][1]=w[root];
for(int i=h[root];i;i=nxt[i]){
int y=to[i];
f[root][1]+=f[y][0];
}
ans+=max(f[root][1],f[root][0]);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1,x;i<=n;++i){
cin>>w[i]>>x;
addedge(x,i);
}
// dfs1(1);
for(int i=1;i<=n;++i){
if(vis[i]==0) dfs1(i);
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号