[IOI2008] Island - 基環樹直徑
阿新 • • 發佈:2020-07-08
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\)
找環的時候如果需要錯位可以借用 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; }