题意:给一颗仙人掌,求它的直径。
有关的定义题目中说的很清楚,就不再重复了。
首先假如给的是一棵树,求树的直径,就比较简单,可以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])
容易得知,整个环一定是按某个顺序访问的
那当回到最高点时,再访问最低点(不是直观的低,意思是dfn最大的点)时才会满足这两个条件。
注意,不能写成这样:
if(tr[y].fa!=x&&dfn[x]==low[x])
如果研究过样例,可以发现这种情况
这种情况最好一个个环处理,如果传一个这么复杂的环进去,起码我是不知道怎么dp啊。
所以我们得到了最高点的和最低点,整个环由他们的fa指针形成一条链,可以很容易的提取出来,然后就是喜闻乐见的dp了。
for(int i=2;i<=tot;i++)f[root]=max(f[root],map[i]+min(i-1,tot-i+1));
还有一个巨大的问题,就是没有统计经过环的边。如图:
这是其实只要在提取出对应点,做一次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<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);
}