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
找环的时候如果需要错位可以借用 std::rotate
#include
using namespace std;
#define int long long
const int N = 1e6+5;
int n,m,t1,t2,t3;
int used[N],vis[N];
vector > g[N];
vector > 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 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 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<