1. 程式人生 > 實用技巧 >[IOI2008] Island - 基環樹直徑

[IOI2008] Island - 基環樹直徑

Description

給出一個基環樹森林,讓你求出所有基環樹的直徑之和。

Solution

基環樹的直徑,要麼是某個子樹的直徑,要麼是兩個子樹的直徑加上一段環上路徑

考慮後者,設 \(f_p\) 表示從 \(p\) 開始到 \(p\) 的子樹中結點的最長路徑

對於環上序號為 \(i,j\) 的兩個點,它們之間可以選擇的有兩條不同的路徑,其貢獻分別為

\[f_i+f_j+sum_j-sum_i,\ f_i+f_j+len-sum_j+sum_i \]

其中 \(len\) 是環的總長,\(sum\) 是環上長度字首和

觀察到在以上兩式中,有效的部分實際上是以 \(f+sum\)\(f-sum\)

整體出現的,因此我們掃描 \(j\) 並記錄所有 \(i<j\) 中最大的 \(f_i - sum_i\)\(f_i + sum_i\) 即可

找環的時候如果需要錯位可以借用 std::rotate

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int N = 1e6+5;

int n,m,t1,t2,t3;
int used[N],vis[N];
vector <pair<signed,signed> > g[N];
vector <pair<signed,signed> > sta;
int cir[N],sum[N],tot,f[N],res;

void dfs(signed p,signed fa,signed fadis)
{
    vis[p]=1;
    sta.push_back({p,fadis});
    for(pair<signed,signed> pr:g[p])
    {
        if(tot) break;
        int q=pr.first, w=pr.second;
        if(q==fa)
        {
            fa=-fa;
            continue;
        }
        if(vis[q])
        {
            tot=1;
            cir[tot]=q;
            sum[tot]=w;
            while(sta.size() && sta.back().first!=q)
            {
                ++tot;
                cir[tot]=sta.back().first;
                sum[tot]=sta.back().second;
                sta.pop_back();
            }
        }
        else
        {
            dfs(q,p,w);
        }
    }
    if(tot==0) sta.pop_back();
}

void dp(int p)
{
    used[p]=1;
    for(pair<signed,signed> pr:g[p])
    {
        int q=pr.first, w=pr.second;
        if(used[q]==0)
        {
            dp(q);
            res=max(res,f[p]+f[q]+w);
            f[p]=max(f[p],f[q]+w);
        }
    }
}

signed main()
{
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>t2>>t3;
        t1=i;
        g[t1].push_back({t2,t3});
        g[t2].push_back({t1,t3});
    }

    int ans=0;
    for(int i=1;i<=n;i++)
    {
        if(used[i]==0)
        {
            int m1=-1e18, m2=-1e18;
            res=0, tot=0;
            dfs(i,0,0);

            rotate(sum+1,sum+tot,sum+tot+1);

            for(int i=1;i<=tot;i++)
            {
                used[cir[i]]=1;
                sum[i]+=sum[i-1];
            }
            for(int i=1;i<=tot;i++)
            {
                dp(cir[i]);
            }
            for(int i=1;i<=tot;i++)
            {
                res=max(res, max(sum[i]+f[cir[i]]+m1, sum[tot]-sum[i]+f[cir[i]]+m2));
                m1=max(m1, f[cir[i]]-sum[i]);
                m2=max(m2, f[cir[i]]+sum[i]);
            }
            ans+=res;
        }
    }
    cout<<ans;
}