[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;
}

 

posted @ 2019-05-05 22:42  X_rice  阅读(199)  评论(0编辑  收藏  举报