洛谷 P4244 [SHOI2008]仙人掌图 II 解题报告

P4244 [SHOI2008]仙人掌图 II

题目背景

题目这个II是和SHOI2006的仙人掌图区分的,bzoj没有。 但是实际上还是和bzoj1023是一个题目的。

题目描述

如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人掌图(cactus)。所谓简单回路就是指在图上不重复经过任何一个顶点的回路。显然,仙人图上的每条边,或者是这张仙人图的桥(bridge),或者在且仅在一个简单回路里,两者必居其一。定义在图上两点之间的距离为这两点之间最短路径的距离。定义一个图的直径为这张图相距最远的两个点的距离。现在我们假定仙人图的每条边的权值都是1,你的任务是求出给定的仙人图的直径。

输入输出格式

输入格式:

输入的第一行包括两个整数n和m(1≤n≤50000以及0≤m≤10000)。其中n代表顶点个数,我们约定图中的顶点将从1到n编号。接下来一共有m行。代表m条路径。每行的开始有一个整数k(2≤k≤1000),代表在这条路径上的顶点个数。接下来是k个1到n之间的整数,分别对应了一个顶点,相邻的顶点表示存在一条连接这两个顶点的边。一条路径上可能通过一个顶点好几次,比如对于第一个样例,第一条路径从3经过8,又从8返回到了3,但是我们保证所有的边都会出现在某条路径上,而且不会重复出现在两条路径上,或者在一条路径上出现两次。

输出格式:

只需输出一个数,这个数表示仙人图的直径长度。


注意直径的定义,距离定义为最小距离,直径定义为最大距离。

然后树边普通dp,环上需要去小距离,把环倍长,每次只留下环的1/2长度的最大值进行更新即可,用单调队列维护一下。

然后我倍长数组没开两倍调了20年...


Code:

#include <cstdio>
#include <algorithm>
using std::min;
using std::max;
const int N=50010;
int head[N],to[N<<4],Next[N<<4],cnt;
void add(int u,int v)
{
    to[++cnt]=v,Next[cnt]=head[u],head[u]=cnt;
}
int n,m,s[N<<1],tot,fa[N],dfn[N],low[N],dfsclock,dp[N],ans,q[N<<1],l,r;
void cal(int rt,int p)
{
    int now=p;tot=0;
    while(now!=rt)
    {
        s[++tot]=now;
        now=fa[now];
    }s[++tot]=rt;
    for(int i=1;i<=tot;i++) s[i+tot]=s[i];
    tot<<=1,l=1,r=0;
    for(int i=1;i<=tot;i++)
    {
        while(l<=r&&i-q[l]>tot>>2) ++l;
        if(l<=r) ans=max(ans,dp[s[i]]+dp[s[q[l]]]+i-q[l]);
        while(l<=r&&dp[s[i]]>=dp[s[q[r]]]+i-q[r]) --r;
        q[++r]=i;
    }
    tot>>=1;
    for(int i=1;i<=tot;i++) dp[rt]=max(dp[rt],dp[s[i]]+min(i,tot-i));
}
void dfs(int now)
{
    dfn[now]=low[now]=++dfsclock;
    for(int v,i=head[now];i;i=Next[i])
        if((v=to[i])!=fa[now])
        {
            if(!dfn[v]) fa[v]=now,dfs(v),low[now]=min(low[now],low[v]);
            else low[now]=min(low[now],dfn[v]);
            if(dfn[now]<low[v]) ans=max(ans,dp[v]+dp[now]+1),dp[now]=max(dp[now],dp[v]+1);
        }
    for(int v,i=head[now];i;i=Next[i])
        if(fa[v=to[i]]!=now&&dfn[v]>dfn[now])
            cal(now,v);
    ans=max(ans,dp[now]);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int k,u,v,i=1;i<=m;i++)
    {
        scanf("%d%d",&k,&u);
        for(int j=1;j<k;j++)
        {
            scanf("%d",&v);
            add(u,v),add(v,u);
            u=v;
        }
    }
    dfs(1);
    printf("%d\n",ans);
    return 0;
}

2018,12,25

转载于:https://www.cnblogs.com/butterflydew/p/10174884.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值