洛谷 P4244 [SHOI2008]仙人掌图 II

62 篇文章 0 订阅

题目链接: https://www.luogu.org/problem/P4244

题意:

无向图仙人掌求直径,即这张图相距最远的两个点的距离,距离为两个点之间的最短路长度。

做法:

必要的过程解释都已经写在代码里了。

简单来说,如果是一棵树,那么直接用 d [ x ] d[x] d[x] 来表示点 x x x 往下的儿子到 x x x 的最长距离,每次用最长和次长距离更新答案即可。

但是因为仙人掌图是存在环的,所以把环上的情况全部存在第一次发现环的这个根节点上。如何存呢,大概的意思就是先将环上的点一次保存,用尺取的方法不断地去用环上最远的两个点之间的距离(加上这些点向外延伸的距离)去更新答案。 最后再把通过这个环可以到达的最远距离更新在根节点上,这个根节点可以再去更新其他值。

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i = (int)a;i<=(int)b;i++)
#define rep_e(i,u,v) for(int i=head[u],v=to[i];~i;i=nex[i],v=to[i])
using namespace std;
typedef long long ll;
const int maxn=50005;
const int maxm=2000005;
int n,m,low[maxn],dfn[maxn],d[maxn];
int head[maxn],to[maxm],nex[maxm],cnt,ans;
int a[maxn*2],st[maxn*2],ct,dep[maxn],fa[maxn];
void add(int u,int v){
    to[cnt]=v;nex[cnt]=head[u];
    head[u]=cnt++;
}
void deal(int rt,int x){
    // 用深度差确定这个环上有多少边
    int tot=dep[x]-dep[rt]+1,tmp=tot;

    //将这个环上的点向外的最远距离记录
    for(int i=x;i!=rt;i=fa[i]){
        a[tmp--]=d[i];
    }
    a[tmp]=d[rt];

    //将点翻倍 便于做环的处理(环中是用的尺取的方法)
    rep(i,1,tot) a[tot+i]=a[i];
    st[1]=1;
    int l=1,r=1;
    rep(i,2,2*tot){

        //如果左端和右端的距离超过一半的环 那么肯定已经不合法
        while(l<=r&&i-st[l]>tot/2) l++;

        //st为递减的单调栈 l存的是最大的a[x]-x
        //表示从i出去的最长边far[i]+far[st[l]]+点st[l]和i在换上的距离
        ans=max(ans,a[i]+i+a[st[l]]-st[l]);

        //维护一个a[i]-i递减的单调栈
        while(l<=r&&a[st[r]]-st[r]<=a[i]-i) r--;
        st[++r]=i;
    }

    //用根节点来存从环延伸出去的最长的边
    rep(i,2,tot) d[rt]=max(d[rt],a[i]+min(i-1,tot-(i-1)));
}
void dfs(int u,int f){
    low[u]=dfn[u]=++ct;
    rep_e(i,u,v){
        if(v==f) continue;
        if(!dfn[v]){
            fa[v]=u; dep[v]=dep[u]+1;   //更新点的父亲以及深度
            dfs(v,u); low[u]=min(low[v],low[u]);
        }
        else low[u]=min(low[u],dfn[v]);
        if(dfn[u]<low[v]){              //该边为非环边 当做树边来做
            ans=max(ans,d[u]+d[v]+1);
            d[u]=max(d[u],d[v]+1);
        }
    }
    rep_e(i,u,v)
        if(fa[v]!=u&&dfn[u]<dfn[v])//找到了环
            deal(u,v);

}
int main(){
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    rep(o,1,m){
        int k,x,y; scanf("%d",&k);
        scanf("%d",&x);
        rep(i,2,k){
            scanf("%d",&y);
            add(x,y); add(y,x); x=y;
        }
    }
    dep[1]=1;
    dfs(1,-1);
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值