您的位置:首页 > 其它

bzoj 1023: [SHOI2008]cactus仙人掌图

2017-08-08 15:52 453 查看


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

有关的定义题目中说的很清楚,就不再重复了。 

首先假如给的是一棵树,求树的直径,就比较简单,可以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]<dfn[y])
1
1

容易得知,整个环一定是按某个顺序访问的 


 

那当回到最高点时,再访问最低点(不是直观的低,意思是dfn最大的点)时才会满足这两个条件。 

注意,不能写成这样:
if(tr[y].fa!=x&&dfn[x]==low[x])
1
1

如果研究过样例,可以发现这种情况 


 

这种情况最好一个个环处理,如果传一个这么复杂的环进去,起码我是不知道怎么dp啊。 

所以我们得到了最高点的和最低点,整个环由他们的fa指针形成一条链,可以很容易的提取出来,然后就是喜闻乐见的dp了。
for(int i=2;i<=tot;i++)
f[root]=max(f[root],map[i]+min(i-1,tot-i+1));
1
2
1
2

还有一个巨大的问题,就是没有统计经过环的边。如图: 


 

这是其实只要在提取出对应点,做一次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));
1
2
3
1
2
3

正确性显然,只是会T到妈都不认得。 

设dis(x,y)表示环上x,y的最短路,易证dis(x,y)<=tot/2。 

所以断环为链,复制一份,显然有单调性,直接单调队列就可有O(n)了。 

code:
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<iostream>
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]<low[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]<dfn[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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: