AIGC标识 基环树找直径

基环树找直径(C++ 图文详解 · 两种跨环做法)

一、基环树的结构

基环树 = 一个环 + 环上每个点挂一棵树。n 个点 n 条边且连通的无向图就是基环树。

graph TD A((环点A)) --- B((环点B)) B --- C((环点C)) C --- D((环点D)) D --- A A --- T1[挂树1] B --- T2[挂树2] C --- T3[挂树3] D --- T4[挂树4] style A fill:#9cf style B fill:#9cf style C fill:#9cf style D fill:#9cf style T1 fill:#c5e1a5 style T2 fill:#c5e1a5 style T3 fill:#c5e1a5 style T4 fill:#c5e1a5

蓝色 = 环上的点,绿色 = 挂在环点上的树。去掉任意一条环边就是一棵普通树,但那样会漏掉“绕远路”的直径,所以必须专门处理。


二、直径只有两类

2a. 完全在一棵挂树内部

graph TD R((环点r)) --- X((x)) X --- Y((y)) X --- Z((z)) Y --- Y1((端点1)) Z --- Z1((端点2)) style R fill:#9cf style Y1 fill:#f96 style Z1 fill:#f96 style X fill:#ffe082

路径 端点1 → y → x → z → 端点2 整段都在挂树里。
用普通树形 DP 求:

  • dp[u]:从 u 往下走的最远距离
  • dia[u]:子树内部直径

转移时保留最长、次长两条向下路径,dia[u]=max(子dia, 最长+次长)。

2b. 跨过环,从一棵挂树到另一棵挂树

graph TD A((环点A)) --- B((环点B)) B --- C((环点C)) C --- D((环点D)) D --- A T1[挂树A最深点] --- A C --- T2[挂树C最深点] style T1 fill:#f96 style T2 fill:#f96 style A fill:#9cf style C fill:#9cf

路径 挂树A最深点 → A →(环上一段)→ C → 挂树C最深点。
长度 = h[A] + h[C] + 环上A到C的弧长,其中 h[i] 是环点 i 往挂树走的最远距离。

关键:环上两点 i,j 之间有两条弧,基环树直径要取较长的那条。


三、找环:拓扑剥叶

graph TD A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A --- L1((叶)) B --- L2((叶)) C --- L3((叶)) D --- L4((叶)) style L1 fill:#ddd style L2 fill:#ddd style L3 fill:#ddd style L4 fill:#ddd style A fill:#9cf style B fill:#9cf style C fill:#9cf style D fill:#9cf

过程:把所有度数为 1 的点(灰色)丢进队列,每次弹出一个,把它的邻居度数 -1;如果邻居度数也变成 1 就继续入队。
结果:最后还剩下的(蓝色)就是环。

queue<int>q;
for(int i=1;i<=n;i++)in_ring[i]=1;
for(int i=1;i<=n;i++)if(deg[i]==1)q.push(i);
while(!q.empty())
{
    int u=q.front();q.pop();
    if(!in_ring[u])continue;
    in_ring[u]=0;
    for(auto [v,w]:g[u])
    {
        if(in_ring[v])
        {
            deg[v]--;
            if(deg[v]==1)q.push(v);
        }
    }
}

四、挂树内部 DP 的图示

对每个环点 r,把它挂着的非环部分单独跑 DP:

graph TD R((环点r)) --- U((u)) U --- V1((v1)) U --- V2((v2)) V1 --- W1((w1)) V1 --- W2((w2)) style R fill:#9cf style V1 fill:#ffe082 style V2 fill:#ffe082
  • dp[v1] = 从 v1 往下走到 w1 或 w2 的较大值
  • dia[v1] = w1 → v1 → w2 这条链的长度(最长 + 次长)
  • dia[u] 还要考虑 v1 子树内或 v2 子树内的直径
  • 最后 h[r]=max(dp[child]+w),dia[r] 还要考虑 最长+次长(两条子树路径跨过 r 连起来)

用 calc(r) 返回 (h[r], 挂树整体直径) 给外层。


五、跨环:两条弧

把环展开成一条链看更直观:

graph LR C0((cyc0)) -->|ww0| C1((cyc1)) -->|ww1| C2((cyc2)) -->|...| CI((cyci)) -->|...| CJ((cycj)) -->|...| CK((cyc k-1)) style CI fill:#9cf style CJ fill:#9cf

设:

  • dist[i] = cyc0 顺时针走到 cyc[i] 的距离
  • L = 环总长 = Σww
  • ww[i] = cyc[i] 到 cyc[i+1] 的边权(ww[k-1] 是 cyc[k-1] 回 cyc0)

对 i<j,两条弧:

正向弧长度 = dist[j]-dist[i]
反向弧长度 = L-(dist[j]-dist[i])

跨环候选:

h[i]+h[j]+max(dist[j]-dist[i], L-(dist[j]-dist[i]))

下面两种做法都能在 O(k) 内处理掉这一部分。


六、方法一:前缀和 + 后缀和

max(a,b) 可以拆成两个式子分别求最大再取 max。于是对 i<j:

6a. 正向弧(走 dist[j]-dist[i])

h[i]+h[j]+dist[j]-dist[i]
= (h[i]-dist[i]) + (h[j]+dist[j])       i<j

固定 j 枚举 i<j,只需前缀最大:

pref[i] = max_{t<=i}(h[t]-dist[t])

对每个 j>=1:

ans = max(ans, pref[j-1] + h[j] + dist[j])
graph LR A["h0-d0"] --> B["max 到 h1-d1"] --> C["max 到 h2-d2"] --> D["..."] --> E["pref[j-1]"] style A fill:#c5e1a5 style E fill:#f96

从左往右扫,pref[j-1] 就是到 j-1 为止 h[t]-dist[t] 的最大值。

6b. 反向弧(走 L-(dist[j]-dist[i]))

h[i]+h[j]+L-dist[j]+dist[i]
= (h[i]+dist[i]) + (h[j]-dist[j]) + L   i<j

固定 i 枚举 j>i,只需后缀最大:

suf[i] = max_{t>=i}(h[t]-dist[t])

对每个 i<=k-2:

ans = max(ans, h[i]+dist[i] + suf[i+1] + L)
graph RL A["h k-1 - d k-1"] --> B["max 到 h k-2 - d k-2"] --> C["..."] --> D["suf[i+1]"] style A fill:#c5e1a5 style D fill:#f96

从右往左扫,suf[i+1] 就是 i+1 之后所有 h[t]-dist[t] 的最大值。

6c. 覆盖性

对任意 i<j,简单路径在环上那段只能选一条弧(正向或反向),取大的那条即最优。两个式子分别枚举两种弧,取 max 即得答案。
i=j 属于同一棵挂树内部,已由 dia[cyc[i]] 覆盖。

复杂度 O(k),不需要复制两倍环。

6d. 核心代码

vector<int>dist(k);
dist[0]=0;
for(int i=1;i<k;i++)dist[i]=dist[i-1]+ww[i-1];
int L=dist[k-1]+ww[k-1];
vector<int>pref(k),suf(k);
pref[0]=h[0]-dist[0];
for(int i=1;i<k;i++)pref[i]=max(pref[i-1],h[i]-dist[i]);
suf[k-1]=h[k-1]-dist[k-1];
for(int i=k-2;i>=0;i--)suf[i]=max(suf[i+1],h[i]-dist[i]);
for(int j=1;j<k;j++)
{
    ans=max(ans,pref[j-1]+h[j]+dist[j]);
}
for(int i=0;i<k-1;i++)
{
    ans=max(ans,h[i]+dist[i]+suf[i+1]+L);
}

七、方法二:单调队列(更常用)

7a. 思路

不拆 max,直接处理:

ans = max_{i<j, j-i<k} ( h[i]+h[j]+dist[j]-dist[i] )
    = max_{i<j, j-i<k} ( h[j]+dist[j] + (h[i]-dist[i]) )

固定 j,我们需要窗口 i∈[j-k+1, j-1] 内 h[i]-dist[i] 的最大值。
这正是滑动窗口最大值,用单调队列维护即可。

但环是闭合的,i 和 j 跨越“零点”的情况(比如 i=k-1, j=0)也要算。把环复制成两倍链 [0,2k) 就统一了:

graph LR C0((cyc0)) --> C1((cyc1)) --> C2((cyc2)) --> C3((...)) --> CK((cyc k-1)) --> C0b((cyc0)) --> C1b((cyc1)) --> C2b((cyc2)) --> C3b((...)) --> CKb((cyc k-1)) style C0 fill:#9cf style C1 fill:#9cf style C2 fill:#9cf style CK fill:#9cf style C0b fill:#c5e1a5 style C1b fill:#c5e1a5 style C2b fill:#c5e1a5 style CKb fill:#c5e1a5

蓝色 = 第一圈,绿色 = 复制出的第二圈,坐标都按 idx%k 映射回原环。

7b. 滑动窗口示意

graph LR P0["0"] --- P1["1"] --- PD["..."] --- PJ["j"] --- PN["2k-1"] style P0 fill:#c5e1a5 style P1 fill:#c5e1a5 style PJ fill:#f96 style PD fill:#fff style PN fill:#ddd

当前扫到 j,窗口是 [j-k+1, j-1],单调队列里存 h[i%k]-dist[i] 递减的下标。
约束 j-i<k 保证不绕回起点,i<j 保证不是同一棵挂树。

7c. 算法流程

队列 dq 存下标,维护 h[i%k]-dist[i] 单调递减
for j in [0, 2k-1]:
    ① 弹出过期队首:dq.front() < j-k+1
    ② 若队列非空,用队首更新答案:
       ans = max(ans, h[j%k]+dist[j] + h[dq.front()%k]-dist[dq.front()])
    ③ 把 j 加入队尾,维护递减:
       while 队尾值 <= h[j%k]-dist[j]: 弹出队尾
       dq.push_back(j)

注意 ② 在 ③ 之前:先算答案再入队,保证 i<j(不和自己配对)。

7d. 核心代码

vector<int>dist(2*k);
dist[0]=0;
for(int i=1;i<2*k;i++)dist[i]=dist[i-1]+ww[(i-1)%k];
deque<int>dq;
for(int j=0;j<2*k;j++)
{
    while(!dq.empty()&&dq.front()<j-k+1)dq.pop_front();
    if(!dq.empty())
    {
        int i=dq.front();
        ans=max(ans,h[j%k]+dist[j]+h[i%k]-dist[i]);
    }
    int val=h[j%k]-dist[j];
    while(!dq.empty()&&h[dq.back()%k]-dist[dq.back()]<=val)dq.pop_back();
    dq.push_back(j);
}

7e. 为什么是 j-k+1

j-i<k ⇔ i>j-k ⇔ 整数 i>=j-k+1。小于 j-k+1 的下标已经绕了整整一圈,会重复经过同一个环点,路径不简单,必须弹出。

j 上界取 2k-1 足够,因为任意有效的 i<j<2k 都能覆盖一对环点。再多加一圈不会更优(j-i<k 已限制)。


八、完整代码

下面给两份都可直接编译运行的版本,只在跨环部分不同。输入格式:n 个点 n 条边,每行 u v w。若边权全为 1,输入 w=1。

版本 A:前缀和 + 后缀和

#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=2e5+5;
const int mod=998244353;
const int inf=1e18;
int n;
// 邻接表:g[u] = {(v,w), ...},无向边存双向
vector<vector<pair<int,int>>>g(N);
// deg:度数(剥叶用)  in_ring:是否仍在环上  vis_ring:环是否已处理
// par:挂树遍历的父节点  dp:子树向下最远距离  dia:子树内部直径
int deg[N],in_ring[N],vis_ring[N],par[N],dp[N],dia[N];

// 处理环点 r 挂着的所有非环子树
// 返回 {h[r], 挂树整体直径}
//   h[r]      = 从 r 往挂树走的最远距离
//   diam      = 挂树内部直径(含跨过 r 的两条子树路径)
pair<int,int>calc(int r)
{
    int diam=0,best1=0,best2=0;  // best1/best2:所有子树对 r 贡献的最长/次长
    for(auto [v,w]:g[r])
    {
        if(in_ring[v])continue;  // 跳过环上的邻居

        // ---------- 迭代版 BFS 建立以 r 为根的子树顺序 ----------
        vector<int>order;
        order.push_back(v);
        par[v]=r;                // 根 v 的父节点是 r
        for(int idx=0;idx<(int)order.size();idx++)
        {
            int u=order[idx];
            for(auto [to,ww]:g[u])
            {
                if(to==par[u]||in_ring[to])continue;  // 不走父亲、不进环
                par[to]=u;
                order.push_back(to);
            }
        }

        // ---------- 逆序做树形 DP ----------
        for(int i=(int)order.size()-1;i>=0;i--)
        {
            int u=order[i];
            dp[u]=0;
            dia[u]=0;
            int b1=0,b2=0;       // 子树中向下的最长/次长
            for(auto [to,ww]:g[u])
            {
                if(to==par[u]||in_ring[to])continue;
                int c=dp[to]+ww;                 // 经过该儿子的向下距离
                dia[u]=max(dia[u],dia[to]);      // 继承儿子的直径
                dia[u]=max(dia[u],b1+c);         // 之前最长 + 当前儿子,跨 u 成链
                if(c>b1)                         // 维护最长/次长
                {
                    b2=b1;
                    b1=c;
                }else if(c>b2)
                {
                    b2=c;
                }
            }
            dp[u]=b1;                            // 从 u 往下的最远
            dia[u]=max(dia[u],b1+b2);            // 两条最长链在 u 汇合
        }

        // ---------- 把整棵子树 v 汇总到 r 上 ----------
        int c=dp[v]+w;                            // 该子树对 r 的贡献长度
        diam=max(diam,dia[v]);                    // 该子树内部直径
        diam=max(diam,best1+c);                   // 之前最优子树 + 当前子树
        if(c>best1)                               // 维护最长/次长
        {
            best2=best1;
            best1=c;
        }else if(c>best2)
        {
            best2=c;
        }
    }
    diam=max(diam,best1+best2);                   // 两条最长子树路径跨 r 连起来
    return{best1,diam};
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    // ---------- 读入 ----------
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        g[u].push_back({v,w});
        g[v].push_back({u,w});
        deg[u]++;
        deg[v]++;
    }

    // ---------- 拓扑剥叶找环 ----------
    queue<int>q;
    for(int i=1;i<=n;i++)in_ring[i]=1;              // 先假设所有点在环上
    for(int i=1;i<=n;i++)if(deg[i]==1)q.push(i);    // 度数为 1 的先入队
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        if(!in_ring[u])continue;                    // 已处理过
        in_ring[u]=0;                                // 从环上剥掉
        for(auto [v,w]:g[u])
        {
            if(in_ring[v])
            {
                deg[v]--;                            // 邻居度数 -1
                if(deg[v]==1)q.push(v);              // 也变成叶子则入队
            }
        }
    }
    // 剩下的 in_ring[i]=1 就是环上的点

    int ans=0;
    // ---------- 逐个环处理(可能有多棵基环树) ----------
    for(int s0=1;s0<=n;s0++)
    {
        if(in_ring[s0]&&!vis_ring[s0])
        {
            // 沿环把点、边依次存入 cyc、ww
            vector<int>cyc;
            vector<int>ww;                            // ww[i] = cyc[i] -> cyc[i+1] 的边权
            int cur=s0,pre=0;
            do
            {
                cyc.push_back(cur);
                vis_ring[cur]=1;
                int nxt=0,nw=0;
                for(auto [v,w]:g[cur])
                {
                    if(in_ring[v]&&v!=pre)            // 沿环走(避开来路)
                    {
                        nxt=v;
                        nw=w;
                        break;
                    }
                }
                ww.push_back(nw);
                pre=cur;
                cur=nxt;
            }while(cur!=s0);
            int k=(int)cyc.size();

            // ---------- 计算每个环点的 h[i] 和挂树直径 ----------
            vector<int>h(k);
            for(int i=0;i<k;i++)
            {
                auto [hh,dd]=calc(cyc[i]);
                h[i]=hh;
                ans=max(ans,dd);                       // 类1:挂树内部直径
            }

            // ---------- 跨环处理:前缀和 + 后缀和 ----------
            // dist[i] = cyc[0] 顺时针到 cyc[i] 的距离
            vector<int>dist(k);
            dist[0]=0;
            for(int i=1;i<k;i++)dist[i]=dist[i-1]+ww[i-1];
            int L=dist[k-1]+ww[k-1];                   // 环总长
            vector<int>pref(k),suf(k);
            // pref[i] = max_{t<=i}(h[t]-dist[t])
            pref[0]=h[0]-dist[0];
            for(int i=1;i<k;i++)pref[i]=max(pref[i-1],h[i]-dist[i]);
            // suf[i]  = max_{t>=i}(h[t]-dist[t])
            suf[k-1]=h[k-1]-dist[k-1];
            for(int i=k-2;i>=0;i--)suf[i]=max(suf[i+1],h[i]-dist[i]);

            // 类2a:正向弧 (i<j 走 dist[j]-dist[i])
            // (h[i]-dist[i]) + (h[j]+dist[j])  取最大
            for(int j=1;j<k;j++)
            {
                ans=max(ans,pref[j-1]+h[j]+dist[j]);
            }
            // 类2b:反向弧 (i<j 走 L-(dist[j]-dist[i]))
            // (h[i]+dist[i]) + (h[j]-dist[j]) + L  取最大
            for(int i=0;i<k-1;i++)
            {
                ans=max(ans,h[i]+dist[i]+suf[i+1]+L);
            }
        }
    }
    cout<<ans<<"\n";
    return 0;
}

版本 B:单调队列

和 A 相比,只有 signed main() 里跨环那段从“求 L + 前后缀”改成“复制两倍 + 单调队列”,其余完全一样。

#include <bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=2e5+5;
const int mod=998244353;
const int inf=1e18;
int n;
// 邻接表:g[u] = {(v,w), ...},无向边存双向
vector<vector<pair<int,int>>>g(N);
// deg:度数(剥叶用)  in_ring:是否仍在环上  vis_ring:环是否已处理
// par:挂树遍历的父节点  dp:子树向下最远距离  dia:子树内部直径
int deg[N],in_ring[N],vis_ring[N],par[N],dp[N],dia[N];

// 处理环点 r 挂着的所有非环子树
// 返回 {h[r], 挂树整体直径}
//   h[r]      = 从 r 往挂树走的最远距离
//   diam      = 挂树内部直径(含跨过 r 的两条子树路径)
pair<int,int>calc(int r)
{
    int diam=0,best1=0,best2=0;  // best1/best2:所有子树对 r 贡献的最长/次长
    for(auto [v,w]:g[r])
    {
        if(in_ring[v])continue;  // 跳过环上的邻居

        // ---------- 迭代版 BFS 建立以 r 为根的子树顺序 ----------
        vector<int>order;
        order.push_back(v);
        par[v]=r;                // 根 v 的父节点是 r
        for(int idx=0;idx<(int)order.size();idx++)
        {
            int u=order[idx];
            for(auto [to,ww]:g[u])
            {
                if(to==par[u]||in_ring[to])continue;  // 不走父亲、不进环
                par[to]=u;
                order.push_back(to);
            }
        }

        // ---------- 逆序做树形 DP ----------
        for(int i=(int)order.size()-1;i>=0;i--)
        {
            int u=order[i];
            dp[u]=0;
            dia[u]=0;
            int b1=0,b2=0;       // 子树中向下的最长/次长
            for(auto [to,ww]:g[u])
            {
                if(to==par[u]||in_ring[to])continue;
                int c=dp[to]+ww;                 // 经过该儿子的向下距离
                dia[u]=max(dia[u],dia[to]);      // 继承儿子的直径
                dia[u]=max(dia[u],b1+c);         // 之前最长 + 当前儿子,跨 u 成链
                if(c>b1)                         // 维护最长/次长
                {
                    b2=b1;
                    b1=c;
                }else if(c>b2)
                {
                    b2=c;
                }
            }
            dp[u]=b1;                            // 从 u 往下的最远
            dia[u]=max(dia[u],b1+b2);            // 两条最长链在 u 汇合
        }

        // ---------- 把整棵子树 v 汇总到 r 上 ----------
        int c=dp[v]+w;                            // 该子树对 r 的贡献长度
        diam=max(diam,dia[v]);                    // 该子树内部直径
        diam=max(diam,best1+c);                   // 之前最优子树 + 当前子树
        if(c>best1)                               // 维护最长/次长
        {
            best2=best1;
            best1=c;
        }else if(c>best2)
        {
            best2=c;
        }
    }
    diam=max(diam,best1+best2);                   // 两条最长子树路径跨 r 连起来
    return{best1,diam};
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    // ---------- 读入 ----------
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        g[u].push_back({v,w});
        g[v].push_back({u,w});
        deg[u]++;
        deg[v]++;
    }

    // ---------- 拓扑剥叶找环 ----------
    queue<int>q;
    for(int i=1;i<=n;i++)in_ring[i]=1;              // 先假设所有点在环上
    for(int i=1;i<=n;i++)if(deg[i]==1)q.push(i);    // 度数为 1 的先入队
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        if(!in_ring[u])continue;                    // 已处理过
        in_ring[u]=0;                                // 从环上剥掉
        for(auto [v,w]:g[u])
        {
            if(in_ring[v])
            {
                deg[v]--;                            // 邻居度数 -1
                if(deg[v]==1)q.push(v);              // 也变成叶子则入队
            }
        }
    }
    // 剩下的 in_ring[i]=1 就是环上的点

    int ans=0;
    // ---------- 逐个环处理(可能有多棵基环树) ----------
    for(int s0=1;s0<=n;s0++)
    {
        if(in_ring[s0]&&!vis_ring[s0])
        {
            // 沿环把点、边依次存入 cyc、ww
            vector<int>cyc;
            vector<int>ww;                            // ww[i] = cyc[i] -> cyc[i+1] 的边权
            int cur=s0,pre=0;
            do
            {
                cyc.push_back(cur);
                vis_ring[cur]=1;
                int nxt=0,nw=0;
                for(auto [v,w]:g[cur])
                {
                    if(in_ring[v]&&v!=pre)            // 沿环走(避开来路)
                    {
                        nxt=v;
                        nw=w;
                        break;
                    }
                }
                ww.push_back(nw);
                pre=cur;
                cur=nxt;
            }while(cur!=s0);
            int k=(int)cyc.size();

            // ---------- 计算每个环点的 h[i] 和挂树直径 ----------
            vector<int>h(k);
            for(int i=0;i<k;i++)
            {
                auto [hh,dd]=calc(cyc[i]);
                h[i]=hh;
                ans=max(ans,dd);                       // 类1:挂树内部直径
            }

            // ---------- 跨环处理:前缀和 + 后缀和 ----------
            // dist[i] = cyc[0] 顺时针到 cyc[i] 的距离
            vector<int>dist(k);
            dist[0]=0;
            for(int i=1;i<k;i++)dist[i]=dist[i-1]+ww[i-1];
            int L=dist[k-1]+ww[k-1];                   // 环总长
            vector<int>pref(k),suf(k);
            // pref[i] = max_{t<=i}(h[t]-dist[t])
            pref[0]=h[0]-dist[0];
            for(int i=1;i<k;i++)pref[i]=max(pref[i-1],h[i]-dist[i]);
            // suf[i]  = max_{t>=i}(h[t]-dist[t])
            suf[k-1]=h[k-1]-dist[k-1];
            for(int i=k-2;i>=0;i--)suf[i]=max(suf[i+1],h[i]-dist[i]);

            // 类2a:正向弧 (i<j 走 dist[j]-dist[i])
            // (h[i]-dist[i]) + (h[j]+dist[j])  取最大
            for(int j=1;j<k;j++)
            {
                ans=max(ans,pref[j-1]+h[j]+dist[j]);
            }
            // 类2b:反向弧 (i<j 走 L-(dist[j]-dist[i]))
            // (h[i]+dist[i]) + (h[j]-dist[j]) + L  取最大
            for(int i=0;i<k-1;i++)
            {
                ans=max(ans,h[i]+dist[i]+suf[i+1]+L);
            }
        }
    }
    cout<<ans<<"\n";
    return 0;
}

九、验证(k=2)

两条环边 ww[0], ww[1] 都连接 0、1,dist[1]=ww[0],L=ww[0]+ww[1]。

graph LR C0((cyc0)) -->|ww0| C1((cyc1)) C1 -->|ww1| C0 style C0 fill:#9cf style C1 fill:#9cf

版本 A:

  • 正向:pref[0]+h[1]+dist[1] = (h[0]-0)+h[1]+ww[0] = h[0]+h[1]+ww[0]
  • 反向:h[0]+0+suf[1]+L = h[0]+(h[1]-ww[0])+(ww[0]+ww[1]) = h[0]+h[1]+ww[1]

版本 B(j 扫一遍):

  • j=1:窗口 [0,0],取 i=0 → h[1]+ww[0]+h[0]
  • j=2:窗口 [1,1],取 i=1 → h[0]+(ww[0]+ww[1])+h[1]-ww[0] = h[0]+h[1]+ww[1]
  • j=3、j=4 是重复情形,不更优

两种方法都得 h[0]+h[1]+max(ww[0],ww[1]),恰好是两条平行边中较长那条连接两棵挂树的最远路径。✔

总复杂度 O(n)(n 点 n 边,线性遍历 + DP)。

posted @ 2026-10-18 11:07  Aries2011  阅读(14)  评论(0)    收藏  举报