题目链接: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; }