題目鏈接: https://www.luogu.org/problem/P4244
題意:
無向圖仙人掌求直徑,即這張圖相距最遠的兩個點的距離,距離爲兩個點之間的最短路長度。
做法:
必要的過程解釋都已經寫在代碼裏了。
簡單來說,如果是一棵樹,那麼直接用 來表示點 往下的兒子到 的最長距離,每次用最長和次長距離更新答案即可。
但是因爲仙人掌圖是存在環的,所以把環上的情況全部存在第一次發現環的這個根節點上。如何存呢,大概的意思就是先將環上的點一次保存,用尺取的方法不斷地去用環上最遠的兩個點之間的距離(加上這些點向外延伸的距離)去更新答案。 最後再把通過這個環可以到達的最遠距離更新在根節點上,這個根節點可以再去更新其他值。
代碼
#include<bits/stdc++.h>
#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;
}