首先不考虑带环的仙人掌,如果只是一棵普通的树,可以通过dp求每棵子树中的最长链和次长链求树的直径。
那么如果dfs的时候遇到了环,应该用环上的两点挂着的最长链加上两点间的距离来更新树的直径,并用环上一点的最长链加上它到环的根的距离来更新环的根的最长链。
选择环上两点来更新直径,为了考虑到所有选择,将环断开并拷贝一份新的衔接在后面,形成长为二倍的串。用dp[i]+dp[j]+j-i(i、j为在串中位置)更新直径,单调队列维护单调递减的dp[i]-i,并且如果当前点和队头的距离超过半个环就队头出队。
dfs的时候注意只用low[v]>dfn[u]的v来更新直径和u的最长链,判断是否有环的时候也注意让环的根成为环中深度最浅的,即判断dfn[v]>dfn[u],使u成为环的根,v为环的最后一个点。

#include<iostream> #include<cstdio> using namespace std; const int N=50010; int n,m,dp[N],ans,dis[N]; int ver[20000010],Next[20000010],head[N],tot,f[N]; void add(int x,int y){ ver[++tot]=y; Next[tot]=head[x]; head[x]=tot; } int tim,dfn[N],low[N],a[2*N],pos[2*N],q[2*N]; void solve(int x,int y){ int cnt=dis[y]-dis[x]+1; for(int i=y;i!=x;i=f[i]){ a[cnt--]=i; } a[cnt]=x; cnt=dis[y]-dis[x]+1; for(int i=1;i<=cnt;i++){ a[i+cnt]=a[i]; } int l=1,r=1; pos[1]=1,q[1]=dp[a[1]]-1; for(int i=2;i<=2*cnt;i++){ while(i-pos[l]>cnt/2&&l<=r)l++; ans=max(ans,q[l]+dp[a[i]]+i); while(l<=r&&q[r]<=dp[a[i]]-i)r--; q[++r]=dp[a[i]]-i; pos[r]=i; } for(int i=2;i<=cnt;i++){ dp[x]=max(dp[x],dp[a[i]]+min(i-1,cnt-i+1)); } } void dfs(int x,int fa){ dfn[x]=low[x]=++tim; f[x]=fa; for(int i=head[x];i;i=Next[i]){ int y=ver[i]; if(y==fa)continue; if(!dfn[y]){ dis[y]=dis[x]+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,dp[x]+dp[y]+1); dp[x]=max(dp[x],dp[y]+1); } } for(int i=head[x];i;i=Next[i]){ int y=ver[i]; if(y==fa)continue; if(f[y]!=x&&dfn[y]>dfn[x]){ solve(x,y); } } } int main() { scanf("%d%d",&n,&m); for(int i=1,k,lst,x;i<=m;i++){ scanf("%d",&k); lst=0; while(k--){ scanf("%d",&x); if(lst){ add(lst,x),add(x,lst); } lst=x; } } dis[1]=1; dfs(1,0); printf("%d",ans); return 0; }