boxmoe_header_banner_img

Hello! 欢迎来到DRheEheAM的blog!

加载中

文章导读

Jun. 25th&26th | 基环树


avatar
DRheEheAM_Garylv229憨毛怪 2026-06-27 64

AI Summary

基环树由n个点n条边构成,有唯一环。通过三道例题讲解其常见处理方法:断环、Tarjan找环、子树直径与单调队列。

孩子们我回来更新了w

基环树

一些基础知识

基环树,可以看做由 nn 个点和 nn 条边构成的图,也可以看做一颗树加上一条边构成的图
所以其有一个明显的性质,存在且仅有一个

这个环赋予了它一些特殊的处理方法 具体来看题目

P2607 [ZJOI2008] 骑士

这是一道经典的基环树例题,而且容易让人想到P1352 没有上司的舞会。这两道题本质相同,只是这道题需要有一点点处理逻辑的改变。

首先我们可以通过输入方式引出一个结论,如果我们从 ii 讨厌的骑士 vvii 建边,每个点入度一定为 11

由此结论我们可以证明,基环树上的环一定是一个由有向边收尾相连的环,我们就可以通过简单的 dfs 来找到这个环。

找到环之后,考虑找到相邻的两个点,分别进行树形 dp,容易证明,答案存在于这两次 dp 的最优解中。

/*---------------------
by DRheEheAM (awa)-----
love hanser forever!---
---------------------*/
#include<bits/stdc++.h>
using namespace std;
#define intc constexpr int
#define int long long
#define Cios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
intc N=1e6+10;
vector <int> g[N];
int n,s1,s2,res,w[N],vis[N],dp[N][2];
void dfs (int u,int rt) {
    vis[u]=1;
    for (int v:g[u]) {
        if (v==rt) {
            s1=u;
            s2=v;
            return;
        }
        if (!vis[v]) dfs(v,rt);
    }
}
int dodp (int u,int rt) {
    dp[u][0]=0;
    dp[u][1]=w[u];
    for (int v:g[u]) {
        if (v==rt) continue;
        dodp(v,rt);
        dp[u][0]+=max(dp[v][0],dp[v][1]);
        dp[u][1]+=dp[v][0];
    }
    return dp[u][0];
}
signed main() {
    Cios;
    cin>>n;
    for (int i=1;i<=n;i++) {
        int v;
        cin>>w[i]>>v;
        g[v].push_back(i);
    }
    for (int i=1;i<=n;i++) {
        if (!vis[i]) {
            s1=s2=0;
            dfs(i,i);
            if (s1) res+=max(dodp(s1,s1),dodp(s2,s2));
        }
    }
    cout<<res<<"\n";
    return 0;
}

P5022 [NOIP 2018 提高组] 旅行 & P5049 [NOIP 2018 提高组] 旅行 加强版

我们直接考虑加强版做法。对于 m=n1m=n-1 时的做法不做讨论

和上一题不同,这道题我们要建双向边。因此,我们使用 Tarjan 算法求环。

求到环之后,可以发现,我们必须断开,且仅能断开一条边。

为了保证最优,我们可以在 dfs 是维护 fallback ,用于记录这个点可以回退到的第一个点编号。
fallback 比目前这个点能到达的最小的点还要大时,我们考虑回退,删除这条边。

/*---------------------
by DRheEheAM (awa)-----
love hanser forever!---
---------------------*/
#include<bits/stdc++.h>
using namespace std;
#define intc constexpr int
#define intl long long
#define Cios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
intc N=5e5+10,inf=0x3f3f3f3f;
int n,m,dfn[N],low[N],dfncnt=0;
int stk[N],top=0;
bool ins[N],onc[N];
bool visd[N],deld=0;
vector<int> g[N],res;
void tarjan(int u,int fa) {
    dfn[u]=low[u]=++dfncnt;
    stk[++top]=u;
    ins[u]=1;
    for (int v:g[u]) {
        if (v==fa) continue;
        if (!dfn[v]) {
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
        }
        else if (ins[v]) low[u] = min(low[u], dfn[v]);
    }
    if (low[u]==dfn[u]) {
        if (stk[top]!=u) { 
            while (top>0) {
                int x=stk[top--];
                ins[x]=0;
                onc[x]=1;
                if (x==u) break;
            }
        }
        else ins[u]=0,top--;
    }
}
void dfs(int u,int fa,int fbk) {
    int j=0;
    for (int i=0;i<g[u].size();i++) {
        int v=g[u][i];
        if (v==fa||visd[v]) continue;
        j=max(j,i+1);
        while (j<g[u].size()&&(g[u][j]==fa||visd[g[u][j]])) j++;
        int nxt=(j<g[u].size())?g[u][j]:fbk;
        if (!deld&&onc[u]&&onc[v]&&nxt==fbk&&v>fbk) {
            deld=1;
            continue;
        }
        visd[v]=1;
        res.push_back(v);
        dfs(v,u,nxt);
    }
}
signed main() {
    Cios;
    cin>>n>>m;
    for (int i=1;i<=m;i++) {
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    for (int i=1;i<=n;i++) sort(g[i].begin(),g[i].end());
    if (m==n) tarjan(1,0);
    visd[1]=1;
    res.push_back(1);
    dfs(1,0,inf);
    for (int i=0;i<n;i++) cout<<res[i]<<(i==n-1?"":" ");
    cout<<"\n";
    return 0;
}

P4381 [IOI 2008] Island

我们容易联想到树的直径,但是直接套用的话难免会一直在环上打转

我们考虑分类讨论,容易发现基环树直径存在两种情况:经过环,或者只存在于一颗挂在环上的子树上。

我们对于每个环上子树求出此子树的直径与最大深度。先将答案更新为所有子树中的直径中的最大值。

然后考虑拆环成链,并对链上的点处理前缀和 sum

之后得到式子,点 ii 和点 jj 间的最长距离为 di+dj+sumisumjd_i+d_j+sum_i-sum_j ,其中 d 表示这个点在环外子树的最大深度。
整理得到 di+sumi+djsumjd_i+sum_i+d_j-sum_j ,于是考虑将 djsumjd_j-sum_j 进行单调队列处理。

/*---------------------
by DRheEheAM (awa)-----
love hanser forever!---
---------------------*/
#include<bits/stdc++.h>
using namespace std;
#define intc constexpr int
#define intl long long
#define Cios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
intc N=1e6+10;
int n,deg[N],outto[N],outw[N];
intl dp[N],mxd[N],res=0;
struct edge {
    int to,w;
};
int a[N*2];
intl sum[N*2];
vector <edge> g[N];
queue <int> q;
signed main() {
    Cios;
    cin>>n;
    for (int i=1;i<=n;i++) {
        int u,l;
        cin>>u>>l;
        g[u].push_back({i,l});
        g[i].push_back({u,l});
        deg[u]++;
        deg[i]++;
        outto[i]=u;
        outw[i]=l;
    }
    for (int i=1;i<=n;i++) {
        if (deg[i]==1) q.push(i);
    }
    while (q.size()) {
        int u=q.front();
        q.pop();
        for (auto [v,w]:g[u]) {
            if (deg[v]>1) {
                mxd[v]=max({mxd[u],mxd[v],dp[v]+dp[u]+w});
                dp[v]=max(dp[v],dp[u]+w);
                if (--deg[v]==1) q.push(v);
            }
        }
    }
    for (int i=1;i<=n;i++) {
        if (deg[i]<=1) continue;
        vector <int> cir;
        int cur=i;
        while (deg[cur]>1) {
            cir.push_back(cur);
            deg[cur]=0;
            cur=outto[cur];
        }
        int m=cir.size();
        intl mxont=0,mxnot=0;
        for (int j=0;j<m;j++) {
            a[j+1]=a[j+1+m]=cir[j];
            mxnot=max(mxnot,mxd[cir[j]]);
        }
        sum[1]=0;
        for (int j=1;j<=2*m;j++) sum[j]=sum[j-1]+outw[a[j-1]];
        deque<int> dq;
        for (int j=1;j<=2*m;j++) {
            while (dq.size()&&j-dq.front()>=m) dq.pop_front();
            if (dq.size()) mxont=max(mxont,dp[a[j]]+dp[a[dq.front()]]+sum[j]-sum[dq.front()]);
            while (dq.size()&&dp[a[j]]-sum[j]>=dp[a[dq.back()]]-sum[dq.back()]) dq.pop_back();
            dq.push_back(j);
        }
        res+=max(mxont,mxnot);
    }
    cout<<res<<"\n";
    return 0;
}



评论(0)

查看评论列表

暂无评论


发表评论

DRheEheAM_Gary Blog