1. 程式人生 > >bzoj1791[IOI2008]Island島嶼(基環樹+DP)

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); }
View Code