bzoj1791[IOI2008]Island岛屿(基环树+DP)
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1791
题目大意:给你一棵n条边的基环树森林,要你求出所有基环树/树的直径之和。n<=1e6
题解:基环树DP写的很少……
树的直径不用解释了,就是NOIP2018D1T3的20分做法,基环树直径,我们可以yy一下然后就能发现答案是2种:1、把环剖去以后其余的子树的直径。2、环上的一部分+选中的2点中各自的最长链。
第一种可以直接dfs求解,对于第二种……我们可以摒弃垃圾的dfs两遍求树的直径的做法,改成DP形式的求树的直径。然后找到一个环时,记录环上的点最深的深度,然后可以把这个环扩展,记录l,r指针,扫描,随便搞一下就能求得答案。
所以本蒟蒻想到的具体做法就是:先topsort一下,把环给找出来,边topsort边DP,然后遇到环再dfs就OK了
不说废话看代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=2e6+7; int n,cnt,tim,hd[N],v[N],nxt[N],w[N],c[N],du[N],vis[N],q[N]; ll ans,d[N],f[N],a[N],b[N]; void add(int x,int y,int z){v[++cnt]=y,nxt[cnt]=hd[x],hd[x]=cnt,w[cnt]=z;} void dfs(int u,int k) { c[u]=k; for(int i=hd[u];i;i=nxt[i])if(!c[v[i]])dfs(v[i],k); } void topsort() { int qs=0,qe=0; for(int i=1;i<=n;i++)if(du[i]==1)q[qe++]=i; while(qs<qe) { int u=q[qs++]; for(int i=hd[u];i;i=nxt[i]) if(du[v[i]]>1) { du[v[i]]--; d[c[u]]=max(d[c[u]],f[u]+f[v[i]]+w[i]); f[v[i]]=max(f[v[i]],f[u]+w[i]); if(du[v[i]]==1)q[qe++]=v[i]; } } } void dp(int x,int tp) { int m=0,y=x,i; do{ a[++m]=f[y]; du[y]=1; for(i=hd[y];i;i=nxt[i]) if(du[v[i]]>1){b[m+1]=b[m]+w[i];y=v[i];break;} }while(i); if(m==2) { int len=0; for(int i=hd[y];i;i=nxt[i])if(v[i]==x)len=max(len,w[i]); d[tp]=max(d[tp],f[x]+f[y]+len); return; } for(int i=hd[y];i;i=nxt[i])if(v[i]==x){b[m+1]=b[m]+w[i];break;} for(int i=1;i<=m;i++)a[m+i]=a[i],b[m+i]=b[m+1]+b[i]; int l=1,r=1; l=r=q[1]=1; for(int i=2;i<2*m;i++) { while(l<=r&&i-q[l]>=m)l++; d[tp]=max(d[tp],b[i]-b[q[l]]+a[i]+a[q[l]]); while(l<=r&&a[q[r]]+b[i]-b[q[r]]<=a[i])r--; q[++r]=i; } } int main() { scanf("%d",&n); for(int i=1,x,y;i<=n;i++)scanf("%d%d",&x,&y),add(i,x,y),add(x,i,y),du[x]++,du[i]++; for(int i=1;i<=n;i++)if(!c[i])dfs(i,++tim); topsort(); for(int i=1;i<=n;i++) if(du[i]>1&&!vis[c[i]])vis[c[i]]=1,dp(i,c[i]),ans+=d[c[i]]; printf("%lld",ans); }