[IOI2008]Island
题目大意:求多棵基环树的直径总和。
思路:对于基环树系列的题,最重要的就是找环,其他操作都是基于环的。本题也是如此:一棵基环树的直径有两种情况:1、经过环上两个节点外加他们子树的最深深度,也就是dis(i,j)+dep[i]+dep[j],其中dep代表从根节点开始所能到达的最深节点,dist代表i到j的最大距离,有两种情况(顺时针或逆时针,取大的那个)。2、环上某个节点子树的直径。
首先如何找环?本来可以用dfs随便找,但是毒瘤出题人卡dfs,那怎么办呢?我们发现这个输入特别妙,他每次给出第i个点通向哪里,我们可以就按这样模拟,直到找到曾经访问过的点就找到了环。
第一种情况:对于处理出来的环,我们要将其破环城链,也就是记录两遍环的节点。然后我们预处理出他们的dep,可以用bfs或dfs求。注意:dep不是直径。对于每个dis(i,j),我们记个前缀和,以环上某个节点为基准,可以列出一个简单式子:ans=Max{(sum[j]-dep[j])+(sum[i]+dep[i])},(因为我们把环拆成两倍,所以涵盖了所有顺时针和逆时针的状态)对这个式子,我们可以枚举i,也就是知道了sum[i]+dep[i]的值,然后我们要求最大的sum[j]-dep[j],这可以用一个单调队列维护。
第二种情况:对每个环上节点求一遍树的直径,我是用树形dp,设dp[x]是以x为根的最长链,我们对于x的任意两个儿子节点i,j,经过x的最长链就是就过i的最长链加dis(x,i),和过j的最长链加dis(x,j),也就是ans=max(dp[i]+dp[j]+dis(x,i)+dis(x,j))(1<=i<j<son[x])。这看似要双重循环,但我们观察到dp[x]包含了x到dp[i]的最长链,所以我们只要枚举另一个子节点j+dis(x,j)和dis[x]相加更新ans,然后用dp[j]+dis(x,j)更新dis[x]。
最后一、二种情况取个max就是答案
注意:这题毒瘤得很,空间开得很小,又卡dfs,因为要破环城链所以要开两倍空间,还有dep不是树的直径,而是最深节点,有些数组要开long long。
代码如下:
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<cstdlib> using namespace std; const int Maxn=1e6+7; int n,dfn[Maxn],id; int last[Maxn],tot,cnt; int vis[Maxn],que[2*Maxn],pd[Maxn],f[Maxn],dvi[Maxn]; long long las,nic,dep[2*Maxn],Q[2*Maxn],dis[Maxn],ans; struct edge{ int to,next,w; }e[4*Maxn]; struct hoop{ int to,w; }fa[Maxn],ring[Maxn]; inline int read(){ int s=0,w=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();} while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar(); return s*w; } inline void add_edge(int x,int y,int w){ e[++tot].to=y; e[tot].next=last[x]; e[tot].w=w; last[x]=tot; return; } inline long long K(int x){ return dep[x]-Q[x]; } inline void find_hoop(int x){ //找树上的环,不能用dfs求 dvi[x]=1; while(1){ int u=fa[x].to; if(dvi[u]){ ring[++cnt].to=u; ring[cnt].w=fa[u].w;dfn[u]=1; for(int i=x;i!=u;i=f[i]) dfn[i]=1,ring[++cnt].to=i,ring[cnt].w=fa[i].w; break; } else{ dvi[u]=1; f[u]=x; } x=u; } return; } inline void dp(int x){ //树形dp vis[x]=1;dfn[x]=1; for(int i=last[x];i;i=e[i].next){ int u=e[i].to; if(vis[u]||pd[u]) continue; dp(u); nic=max(nic,dis[x]+dis[u]+e[i].w); dis[x]=max(dis[x],dis[u]+e[i].w); } return; } void dfs(int x,int f,long long w){ //求环上每个节点的dep值 if(w>=nic) nic=w; for(int i=last[x];i;i=e[i].next){ int u=e[i].to; if(u!=f&&!pd[u]) dfs(u,x,w+e[i].w); } } inline void first(int i){ nic=0;dfs(ring[i].to,0,0);dep[i]=nic;//!!! nic=0;dp(ring[i].to); las=max(las,nic); return; } inline void quee(){ //单调队列优化 int head=1,tail=0; for(int i=1;i<=2*cnt;++i){ if(i<=cnt) Q[i]=Q[i-1]+ring[i].w; else Q[i]=Q[i-1]+ring[i-cnt].w; if(head<=tail) las=max(las,K(que[head])+Q[i]+dep[i]); while(head<=tail&&K(que[tail])<=K(i)) --tail; que[++tail]=i; while(head<=tail&&que[head]<=i-cnt+1) ++head; } ans+=las; return; } int main(){ int y,w; n=read(); for(int i=1;i<=n;++i){ y=read();w=read(); add_edge(i,y,w);add_edge(y,i,w); fa[i].to=y;fa[i].w=w; //记录方便枚举 } for(int i=1;i<=n;++i){ if(!dfn[i]){ cnt=0;id=0;las=0; find_hoop(i); for(int j=1;j<=cnt;++j) pd[ring[j].to]=1; for(int j=1;j<=cnt;++j) first(j); for(int j=1;j<=cnt;++j) dep[j+cnt]=dep[j]; //把环断成链 quee(); } } printf("%lld\n",ans); return 0; }