bzoj 1023: [SHOI2008]cactus仙人掌图

题意:给一颗仙人掌,求它的直径。

有关的定义题目中说的很清楚,就不再重复了。
首先假如给的是一棵树,求树的直径,就比较简单,可以dfs或bfs。
考虑dp的做法。
设集合g表示i到其各个子树的最长链链,即以i为最高点,且除端点外,没有相交的不同最长链。(语文死得早,意会下吧)
于是过i,且完全在其子树中的最长链是g中最大的+次大的。其长度记为F。
直径显然存在唯一最高点,所以ans=Max{F1,F2,F3,……Fn,}
有个不错的求法,f[i]表示i为最高点的最长链,每次更新完儿子y后用f[x]+f[y]+1更新答案,再用f[y]+1更新f[x]。拓扑序dp就行了。
根据仙人掌图的定义:同一条边至多出现在一个环中,可知若将原图缩点后(一个点是一个简单环或几个并在一起),一定是一棵树,因为若还出现环,就不符合定义了。不连通?不存在的事。
我说的一个环的最高点,是离根最近,也就是最先访问的点。他的状态可以表示整个环的状态,因为对于他的祖先来说,只有他是有用的,而他们的儿子,因为按照dfs序,已经访问完了,更没有影响。
要缩点当然要上tarjan了,但有些不同,大概是一边缩一边dp,然后用自己的状态更新父亲状态(一波蒟蒻口胡)
dfn还是表示dfs序,low可以理解为所处的环中最高点的编号,一个点就假装是环吧。
那么对于所有边可以分为两类,桥和环中的边,也就是树边和非树边(纯属蒟蒻瞎定义)
当一个点x并没有处于环中时,直接按上面说的那样dp就可以了,此时dfn[x]=low[x],且对于他所有儿子y都有low[y]大于dfn[x]。
当你发现low[y]<=dfn[x]是,说明x,y一定形成了环,这时就直接跳过不更新答案,访问别的点,f数组暂时不考虑有环的情况,最后找回环的最高点更新。
但问题是怎么找回最高点?
难以描述,直接上代码

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

容易得知,整个环一定是按某个顺序访问的
bzoj 1023: [SHOI2008]cactus仙人掌图_第1张图片
那当回到最高点时,再访问最低点(不是直观的低,意思是dfn最大的点)时才会满足这两个条件。
注意,不能写成这样:

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

如果研究过样例,可以发现这种情况
bzoj 1023: [SHOI2008]cactus仙人掌图_第2张图片
这种情况最好一个个环处理,如果传一个这么复杂的环进去,起码我是不知道怎么dp啊。
所以我们得到了最高点的和最低点,整个环由他们的fa指针形成一条链,可以很容易的提取出来,然后就是喜闻乐见的dp了。

for(int i=2;i<=tot;i++)
        f[root]=max(f[root],map[i]+min(i-1,tot-i+1));

还有一个巨大的问题,就是没有统计经过环的边。如图:
bzoj 1023: [SHOI2008]cactus仙人掌图_第3张图片
这是其实只要在提取出对应点,做一次dp就可以了,或者都不算dp。

for(int i=1;i<=tot;i++)
        for(int j=i+1;j<=tot;j++)
            ans=max(ans,map[i]+map[j]+min(j-i,i+tot-j));

正确性显然,只是会T到妈都不认得。
设dis(x,y)表示环上x,y的最短路,易证dis(x,y)<=tot/2。
所以断环为链,复制一份,显然有单调性,直接单调队列就可有O(n)了。
code:

#include
#include
#include
#include
#include
using namespace std;
const int inf=(1<<28);
struct node{int y,next;}a[2000005];int last[50010],len=0;
int dfn[50010],low[50010],n,m,f[50010],ans=-inf,id=0;
struct trnode{
    int dep,fa;
}tr[50010];
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 ins(int x,int y)
{
    a[++len].y=y;
    a[len].next=last[x];last[x]=len;
}
int map[100010],q[100010],l,r;
void dp(int root,int x)//最高点和最低点 
{
    int tot=tr[x].dep-tr[root].dep+1;
    for(int i=x;i!=root;i=tr[i].fa)
        map[tot--]=f[i];
    map[tot]=f[root];
    tot=tr[x].dep-tr[root].dep+1;
    for(int i=1;i<=tot;i++) map[i+tot]=map[i];
    q[1]=1;l=1;r=1;
    for(int i=2;i<=tot+1;i++)//单调队列 
    {
        while(l<=r&&i-q[l]>tot/2) l++;
        int j=q[l];
        ans=max(ans,map[i]+map[j]+i-j);
        while(l<=r&&map[q[r]]-q[r]<=map[i]-i) r--;
        q[++r]=i;
    }
    for(int i=2;i<=tot;i++)//更新最高点 
        f[root]=max(f[root],map[i]+min(i-1,tot-i+1));
}

void dfs(int x,int fa)
{
    dfn[x]=low[x]=++id;tr[x].fa=fa;tr[x].dep=tr[fa].dep+1;
    for(int i=last[x];i;i=a[i].next)
    {
        int y=a[i].y;
        if(y==fa) continue;
        if(dfn[y]==-1)
        {
            dfs(y,x);
            low[x]=min(low[x],low[y]);
        }
        else low[x]=min(low[x],dfn[y]);
        if(dfn[x]y])//非环情况 
        {
            ans=max(ans,f[y]+f[x]+1);
            f[x]=max(f[y]+1,f[x]);
        }
    }
    for(int i=last[x];i;i=a[i].next)
    {
        int y=a[i].y;
        if(tr[y].fa!=x&&dfn[x]y])//找最高点 
            dp(x,y);
    }
}
int main()
{
    n=read();m=read();
    memset(last,0,sizeof(last));
    for(int i=1;i<=m;i++)
    {
        int k,x;k=read();x=read();
        for(int j=2;j<=k;j++)
        {
            int y;y=read();
            ins(x,y);ins(y,x);x=y;
        }
    }
    memset(f,0,sizeof(f));
    memset(dfn,-1,sizeof(dfn));
    tr[0].dep=0;dfs(1,0);
    printf("%d",ans);
}

你可能感兴趣的:(仙人掌图)