题目链接:Click here
Solution:
一句话题意:给定基环树森林,求每颗基环树的直径之和
考虑基环树求直径的方法,考虑基环树套路,把环和树分开考虑
我们先把环找出来,对环上的点的子树求直径,再求出这个点开始的最长链,这个很简单,可以用treedp做
考虑一颗基环树的直径可能有哪些情况:基环树上某棵子树的直径或是一棵子树走到另一颗子树
第一种情在我们dp的时候就处理完了,考虑第二种情况怎么做
从一颗子树走到另一颗子树肯定是要经过环上的点的,所以第二种情况必然是一种这样的形式\(f[x]+f[y]+dis(x,y)\)
其中\(f[x]\)表示以\(x\)为起点,终点在其子树内的最长链的长度,在dp中已经处理好了,\(dis(x,y)\)表示环上两点\(x,y\)的距离
我们把环拆成链来考虑,再把这条链倍长,这样它就能描述任意\(dis(x,y)\)了
我们考虑记\(dis[x]\)表示\(x\)到链上第一个点的距离,那么\(dis(x,y)=dis[x]-dis[y]\)(这里我们钦定\(x\)在\(y\)之后)则直径可被表述为\(f[x]+f[y]+dis[x]-dis[y]\)
倘若我们直接对于环上每个点枚举另外一个点来算直径,时间复杂度是\(O(n^2)\)的,考虑如何来优化
考虑把上面的式子转化形式:\(f[x]+dis[x]+f[y]-dis[y]\),我们确定了\(x\),则\(f[y]-dis[y]\)越大越好,那么就很显然的单调队列优化,我们用单调队列来维护\(f[x]-dis[x]\)的值即可,时间复杂度\(O(n)\)
找环可以用拓扑排序,然后本题需要开long long
Code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e6+11;
int n,cnt,head[N],d[N];
int tot,ring[N],vis[N],id[N];
int hd,tl,v[N],son[N];
ll edis,ans,dis[N],f[N],g[N];
queue<int> q;
struct Edge{int nxt,to,val;}edge[N<<1];
void ins(int x,int y,int z){
edge[++cnt].nxt=head[x];
edge[cnt].to=y;head[x]=cnt;
edge[cnt].val=z;
}
void dfs(int x){
ring[++tot]=x;vis[x]=1;id[x]=tot;
for(int i=head[x];i;i=edge[i].nxt){
int y=edge[i].to;
if(id[y]==1&&id[x]==tot) edis=edge[i].val;
if(vis[y]) continue;
dis[tot+1]=edge[i].val;dfs(y);
}
}
void treedp1(int x,int fa){
for(int i=head[x];i;i=edge[i].nxt){
int y=edge[i].to;
if(id[y]||y==fa) continue;
int w=edge[i].val;
treedp1(y,x);
if(f[x]<f[y]+w){
g[x]=f[x];f[x]=f[y]+w;
son[x]=y;
}else if(g[x]<f[y]+w) g[x]=f[y]+w;
}
}
void treedp2(int x,int fa,ll &Mx){
if(f[x]+g[x]>Mx) Mx=f[x]+g[x];
for(int i=head[x];i;i=edge[i].nxt){
int y=edge[i].to;
if(id[y]||y==fa) continue;
int w=f[x]+edge[i].val;
if(y==son[x]) w=g[x]+edge[i].val;
if(f[y]<w){g[y]=f[y];f[y]=w;son[y]=x;}
else if(g[y]<w) g[y]=w;
treedp2(y,x,Mx);
}
}
void work(){
ll re=0,mxdis=0;hd=1,tl=0;
for(int i=1;i<=tot;i++){
treedp1(ring[i],0);
treedp2(ring[i],0,mxdis);
}int limit=tot;
ring[++tot]=ring[1];
dis[tot]=edis;
for(int i=2;i<=limit;i++){
ring[++tot]=ring[i];
dis[tot]=dis[tot-limit];
}
for(int i=2;i<=tot;i++) dis[i]+=dis[i-1];
for(int i=1;i<=tot;i++){
int x=ring[i];
while(hd<=tl&&i-v[hd]>=limit) ++hd;
if(hd<=tl) re=max(re,f[ring[v[hd]]]-dis[v[hd]]+f[x]+dis[i]);
while(tl>=hd&&f[ring[v[tl]]]-dis[v[tl]]<f[x]-dis[i]) --tl;v[++tl]=i;
}re=max(re,mxdis);ans+=re;
}
void topsort(){
for(int i=1;i<=n;i++)
if(d[i]==1) q.push(i),vis[i]=1;
while(!q.empty()){
int x=q.front();q.pop();
for(int i=head[x];i;i=edge[i].nxt){
int y=edge[i].to;
if(vis[y]) continue;
--d[y];if(d[y]==1) vis[y]=1,q.push(y);
}
}
for(int i=1;i<=n;i++){
if(vis[i]) continue;
tot=0;dfs(i);work();
}
}
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-f;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
signed main(){
n=read();
for(int i=1;i<=n;i++){
int x=read(),z=read();
ins(i,x,z),ins(x,i,z);++d[x],++d[i];
}topsort();printf("%lld\n",ans);
return 0;
}