bzoj1023: [SHOI2008]cactus仙人掌图

终于知道仙人掌是什么东西了。。
用我的话来讲,就是一张连通图,然后其中的每一条边至多存在于一个环里面
然后这题就是给你一颗仙人掌,然后问你他最远点的距离(也叫做他的直径)
然后两个点之间的距离就是他们的最短距离啦

解法

首先,我们先对仙人掌当树一样dfs一次
首先,我们定义一个数组f[i],表示从i这个节点出发,不走存在于环的边的最长长度,我叫他为“简单路”
当然一个环总该有一个深度最小的点吧,我们称之为最高点
然后最高点比较特殊,他的f可以走环边
其实,换一句话来讲,全部的f都可以走环边,但是除了最高点,他们用了是没有意义的,只有最高点存储就可以了
这里写图片描述
大概效果就是这样,圈里面的是f,右上角是深度
然后能和上层有联系的就是最高点了
至于为什么,因为他是一个仙人掌嘛,要是比如说1和上层有联系,那么”最”到1这条边就存在于两个环里面了。。其他如此类推

对于x(父亲)和y(儿子)不在一个环上面,那么y就应该是一个最高点,他的信息都是对的
然后可以进行一个十分简单的DP

if (dfn[x]<low[y])//这说明无论怎么样,y和x都不在一个环里面
{
    ans=mymax(ans,f[y]+f[x]+1);
    f[x]=mymax(f[y]+1,f[x]);
}   

感觉这个挺好理解的
至于在一个环上面,就只用在最高点进行DP就好
当然你会说边不可以在两个环上,但点可以啊!
这没关系,我们吧每一个“最小的环拿出来”就好了
也就是只要一有回路就跑一次
怎么找回路呢

if(tr[y].fa!=x&&dfn[x]<dfn[y])

这样就可以了。。同时我们可以知道x就是这个环的最高点
那么知道了最高点,我们就可以用一个DP来更新最高点的f值了

    for (int u=2;u<=tot;u++)
        f[root]=mymax(f[root],map[u]+mymin(u-1,tot-u+1));

但是,FYC蒟蒻说还有一种情况没有考虑
就是从一个不是最高点的两个点进来,然后绕一圈出去这种路径没有算
这里写图片描述
大概就是上图中标红的路径
于是这个可以枚举两个点,然后暴力看一下

for (int u=1;u<=tot;u++)
        for (int i=u+1;i<=tot;i++)
            ans=mymax(ans,map[u]+map[i]+mymin(i-u,u+tot-i));

但是这是会T的
然后可以单调队列优化一下。。

#include<cstdio>
#include<cstring>
const int N=50005;
const int MAX=1<<30;
struct qq{int x,y,last;}s[2000005];
int num,last[N];
int n,m;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void init (int x,int y)
{
    num++;
    s[num].x=x;s[num].y=y;
    s[num].last=last[x];
    last[x]=num;
}
int f[N];//f[i]表示从i出发,只可以走一条“简单路”的最远长度
int dfn[N],low[N],id;//这个就是这个点dfs的编号   low返祖点
int dep[N],fa[N];//深度    父亲
int ans=0;
int mymin (int x,int y){return x<y?x:y;}
int mymax (int x,int y){return x>y?x:y;}
int map[100010];
int q[100010],l,r;
void DP (int root,int x)
{
    int tot=dep[x]-dep[root]+1;
    for (int i=x;i!=root;i=fa[i])
        map[tot--]=f[i];
    map[tot]=f[root];
    tot=dep[x]-dep[root]+1;
    /*for (int u=1;u<=tot;u++)
        for (int i=u+1;i<=tot;i++)
            ans=mymax(ans,map[u]+map[i]+mymin(i-u,u+tot-i));*/
    map[tot+1]=map[1];
    q[1]=1;l=r=1;
    for (int u=2;u<=tot+1;u++)
    {
        while (l<=r&&u-q[l]>tot/2) l++;
        int j=q[l];
        ans=mymax(ans,map[u]+map[j]+u-j);
        while (l<=r&&map[q[r]]-q[r]<map[u]-u) r--;
        q[++r]=u;
    }
    for (int u=2;u<=tot;u++)
        f[root]=mymax(f[root],map[u]+mymin(u-1,tot-u+1));
}
void dfs (int x,int F)
{
//  printf("%d %d\n",x,F);
    dfn[x]=low[x]=++id;
    fa[x]=F;dep[x]=dep[F]+1;
    for (int u=last[x];u!=-1;u=s[u].last)
    {
        int y=s[u].y;
        if (y==F) continue;
        if (dfn[y]==-1)//这个点没有访问过
        {
            dfs(y,x);
            low[x]=mymin(low[x],low[y]);
        }
        else low[x]=mymin(low[x],dfn[y]);
        if (dfn[x]<low[y])//这说明无论怎么样,yx都不在一个环里面
        {
            ans=mymax(ans,f[y]+f[x]+1);
            f[x]=mymax(f[y]+1,f[x]);
        }   
    }
    for (int u=last[x];u!=-1;u=s[u].last)
    {
        int y=s[u].y;
        if (fa[y]!=x&&dfn[x]<dfn[y])//这是这个环的最高点
            DP(x,y);
    }
}
int main()
{
    num=0;memset(last,-1,sizeof(last));
    n=read();m=read();
    for (int u=1;u<=m;u++)
    {
        int k=read(),x=read();
        for (int u=1;u<k;u++)
        {
            int y=read();
            init(x,y);init(y,x);x=y;
        }
    }
    memset(f,0,sizeof(f));
    memset(dfn,-1,sizeof(dfn));
    dep[0]=0;dfs(1,0);
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值