[IOI2008]Island 传送门
本题给定几个基环树的森林,求每棵基环树的直径长度之和
思路:
先单独考虑一棵基环树,先dfs找环(也可tarjan),先把每个环上的点向外扩展,找出它们每棵子树的直径,用树型DP处理,接着拆环成链,再复制一倍的链长,在环上跑,用单调队列维护即可,但是维护前还需要预处理下直径前缀和,否则会爆时间。
代码来自wss巨佬:
#include<bits/stdc++.h>
#define N 1000005
using namespace std;
struct node{
int to,nxt;long long val;
}e[2000005];
long long n,ans,tot=1,head[N],z[N],zz,js,dfn[N],fa[N];
bool vis[N],cir[N];//cir是否在环中
void build(int a,int b,long long c)
{
e[++tot].to=b;
e[tot].nxt=head[a];
e[tot].val=c;
head[a]=tot;
}
void dfs1(int p,int ei)//找环
{
vis[p]=true;
dfn[p]=++js;
for(int i=head[p];i;i=e[i].nxt)
{
int v=e[i].to;
if(i!=ei)
{
if(!dfn[v])
{
fa[v]=p;
dfs1(v,i^1);
}
else if(dfn[v]>dfn[p])
{
for(int j=v;j!=p;j=fa[j]) z[++zz]=j,cir[j]=true;
z[++zz]=p,cir[p]=true;
}
}
}
}
long long d[N],mx;
void dp(int p,int fa)
{
for(int i=head[p];i;i=e[i].nxt)
{
int v=e[i].to;
if(v!=fa&&!cir[v])
{
dp(v,p);
mx=max(mx,d[p]+d[v]+e[i].val;)
d[p]=max(d[p],d[v]+e[i].val;)
}
}
}
long long sm[N*2],hc[N*2],nn,pos[N*2],l,r;//pos存储队列
void dfs2(int p,int i,int ei)//预处理直径前缀和
{
if(i==nn)return;
for(int j=head[p];j;j=e[j].nxt)
{
if(e[j].to==hc[i+1]&&j!=ei)
{
sm[i]=sm[i-1]+e[j].val;
dfs2(e[j].to,i+1,j^1);
return;
}
}
}
void work(int p)
{
mx=0;
zz=0;
dfs1(p,-1);
for(int i=1;i<=zz;i++)//把环上每个点向外扩展,找环外边的每棵子树的直径
{
dp(z[i],0);
}
for(int i=1;i<=zz;i++) hc[i]=hc[i+zz]=z[i];
nn=zz+zz;
dfs2(z[1],1,0);
l=1;r=0;
for(int i=1;i<=nn;i++)//单调队列
{
while(l<=r&&i-pos[l]>=zz)++l;
if(l<=r&&sm[i-1]-sm[pos[l]-1]+d[hc[pos[l]]]+d[hc[i]]>mx)
mx=sm[i-1]-sm[pos[l]-1]+d[hc[pos[l]]]+d[hc[i]];
while(l<=r&&(i-pos[r]>=zz||sm[i-1]+d[hc[pos[r]]]<sm[pos[r]-1]+d[hc[i]]))--r;
pos[++r]=i;
}
ans+=mx;
}
int main()
{
scanf("%lld",&n);
long long a,b;
for(int i=1;i<=n;i++)
{
scanf("%lld%lld",&a,&b);
build(i,a,b);
build(a,i,b);
}
for(int i=1;i<=n;i++)
{
if(!vis[i])
work(i);
}
printf("%lld",ans);
}