题目链接: https://www.luogu.org/problem/P4244
题意:
无向图仙人掌求直径,即这张图相距最远的两个点的距离,距离为两个点之间的最短路长度。
做法:
必要的过程解释都已经写在代码里了。
简单来说,如果是一棵树,那么直接用 d [ x ] d[x] d[x] 来表示点 x x x 往下的儿子到 x x x 的最长距离,每次用最长和次长距离更新答案即可。
但是因为仙人掌图是存在环的,所以把环上的情况全部存在第一次发现环的这个根节点上。如何存呢,大概的意思就是先将环上的点一次保存,用尺取的方法不断地去用环上最远的两个点之间的距离(加上这些点向外延伸的距离)去更新答案。 最后再把通过这个环可以到达的最远距离更新在根节点上,这个根节点可以再去更新其他值。
代码
#include
#define rep(i,a,b) for(int i = (int)a;i<=(int)b;i++)
#define rep_e(i,u,v) for(int i=head[u],v=to[i];~i;i=nex[i],v=to[i])
using namespace std;
typedef long long ll;
const int maxn=50005;
const int maxm=2000005;
int n,m,low[maxn],dfn[maxn],d[maxn];
int head[maxn],to[maxm],nex[maxm],cnt,ans;
int a[maxn*2],st[maxn*2],ct,dep[maxn],fa[maxn];
void add(int u,int v){
to[cnt]=v;nex[cnt]=head[u];
head[u]=cnt++;
}
void deal(int rt,int x){
// 用深度差确定这个环上有多少边
int tot=dep[x]-dep[rt]+1,tmp=tot;
//将这个环上的点向外的最远距离记录
for(int i=x;i!=rt;i=fa[i]){
a[tmp--]=d[i];
}
a[tmp]=d[rt];
//将点翻倍 便于做环的处理(环中是用的尺取的方法)
rep(i,1,tot) a[tot+i]=a[i];
st[1]=1;
int l=1,r=1;
rep(i,2,2*tot){
//如果左端和右端的距离超过一半的环 那么肯定已经不合法
while(l<=r&&i-st[l]>tot/2) l++;
//st为递减的单调栈 l存的是最大的a[x]-x
//表示从i出去的最长边far[i]+far[st[l]]+点st[l]和i在换上的距离
ans=max(ans,a[i]+i+a[st[l]]-st[l]);
//维护一个a[i]-i递减的单调栈
while(l<=r&&a[st[r]]-st[r]<=a[i]-i) r--;
st[++r]=i;
}
//用根节点来存从环延伸出去的最长的边
rep(i,2,tot) d[rt]=max(d[rt],a[i]+min(i-1,tot-(i-1)));
}
void dfs(int u,int f){
low[u]=dfn[u]=++ct;
rep_e(i,u,v){
if(v==f) continue;
if(!dfn[v]){
fa[v]=u; dep[v]=dep[u]+1; //更新点的父亲以及深度
dfs(v,u); low[u]=min(low[v],low[u]);
}
else low[u]=min(low[u],dfn[v]);
if(dfn[u]<low[v]){ //该边为非环边 当做树边来做
ans=max(ans,d[u]+d[v]+1);
d[u]=max(d[u],d[v]+1);
}
}
rep_e(i,u,v)
if(fa[v]!=u&&dfn[u]<dfn[v])//找到了环
deal(u,v);
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
rep(o,1,m){
int k,x,y; scanf("%d",&k);
scanf("%d",&x);
rep(i,2,k){
scanf("%d",&y);
add(x,y); add(y,x); x=y;
}
}
dep[1]=1;
dfs(1,-1);
printf("%d\n",ans);
return 0;
}