基环树算法总结

基环树算法总结

一、什么是基环树

基环树,顾名思义,有两个要素:环和树。

因此,基环树就是一棵树的一个节点,扩成一个环,做题时,多棵基环树组成的基环树森林,常以如下方式出现:

  1. 每个点只有一个出边。
  2. 每个点只有一个入边。
  3. 图中一共有 \(n\) 个点,\(n\) 条边。

那么,基环树类型的题目应该怎么做呢?

二、基环树怎么做

先来看看这道题:\([ZJOI2008]\) 骑士

此题和没有上司的舞会几乎一模一样,不一样的是,这道题是基环树。

考虑假如这道题在树上就会简单很多,那么我们就会发现,假如断掉环上的一条边,那么原来的基环树就变成了一棵树,树形 dp 即可。

#include<bits/stdc++.h>
#define ll long long
#define N 1000005
using namespace std;
int n,m,a,h[N],w[N];
int to[N*2],nxt[N*2];
int vis[N],fa[N];
int q1[N],q2[N],l,r;
ll dp1[N],dp2[N],ans;
void add(int x,int y){
    to[++m]=y;
    nxt[m]=h[x];
    h[x]=m;
}void dp_(int x,int f,int rt){
    vis[x]=1;
    dp1[x]=0;
    dp2[x]=w[x];
    for(int i=h[x];i;i=nxt[i]){
        int y=to[i];
        if(y==a)
            continue;
        dp_(y,x,rt);
        dp1[x]+=max(dp1[y],dp2[y]);
        dp2[x]+=dp1[y];
    }
}int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++){
        int j;
        cin>>w[i]>>j;
        add(j,i);
        fa[i]=j;
    }for(int i=1;i<=n;i++){
        if(vis[i]) continue;
        vis[(a=i)]=1;
        while(!vis[fa[a]]){
            a=fa[a];
            vis[a]=1;
        }dp_(a,0,a);
        ll cc=dp1[a];
        a=fa[a];
        dp_(a,0,a);
        ans+=max(cc,dp1[a]);
    }cout<<ans;
    return 0;
}

接着我再介绍一下我求基环树(单向图)时常用的找环方法(其中 \(a_i\) 表示 \(i\) 的出/入边与 \(a_i\) 相连):

在进行并查集时,无条件将 \(i\) 置于 \(a_i\) 下。这样可以保证每个根节点都在环上,使判环更便捷、更迅速。

posted @ 2024-04-20 11:23  长安一片月_22  阅读(9)  评论(0编辑  收藏  举报