解题思路:
f[i]表示以i为根的子树中i为起点的最长链。
若(u,v)是桥,则ans=max(ans,f[u]+f[v]+1)(此时f[u]还未被f[v]更新),接着f[u]=max(f[u],f[v]+1);这也是树的直径的一种解法。
若u是环上的一个点,设该环大小为cnt,则ans=max(ans,f[u]+f[v]+dis(u,v))其中v也为该环上一点且dis(u,v)<=cnt/2(不然就走环的另一侧了),所以可以用单调队列维护dp。
处理完一个环后,假设u是dfs时进入该环的点,那么f[u]=max(f[v]+dis(v,u)),其中v为该环上一点且dis(u,v)<=cnt/2。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<vector>
#include<queue>
#define ll long long
using namespace std;
int getint()
{
int i=0,f=1;char c;
for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
if(c=='-')c=getchar(),f=-1;
for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
return i*f;
}
const int N=50005;
int n,m,k,ans;
vector<int>g[N];
int idx,dfn[N],low[N],fa[N];
int f[N],a[N<<1],q[N<<1];
void dp(int root,int u)
{
int cnt=0;
while(u!=root)a[++cnt]=f[u],u=fa[u];
a[++cnt]=f[root];reverse(a+1,a+cnt+1);
for(int i=cnt+1;i<=cnt*2;i++)a[i]=a[i-cnt];
int head=1,tail=1;
q[tail]=1;
for(int i=2;i<=cnt+cnt/2;i++)
{
while(head<tail&&q[head]<i-cnt/2)head++;
ans=max(ans,a[i]+a[q[head]]+i-q[head]);
while(head<tail&&a[i]-i>a[q[tail]]-q[tail])tail--;
q[++tail]=i;
}
for(int i=2;i<=cnt;i++)
f[root]=max(f[root],a[i]+min(i-1,cnt-i+1));
}
void tarjan(int u)
{
dfn[u]=low[u]=++idx;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==fa[u])continue;
if(!dfn[v])
{
fa[v]=u;
tarjan(v);
low[u]=min(low[u],low[v]);
}
else low[u]=min(low[u],dfn[v]);
if(dfn[u]<low[v])
ans=max(ans,f[u]+f[v]+1),f[u]=max(f[u],f[v]+1);
}
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(fa[v]!=u&&dfn[u]<dfn[v])
dp(u,v);
}
}
int main()
{
//freopen("lx.in","r",stdin);
//freopen("lx.out","w",stdout);
int x,y;
n=getint(),m=getint();
while(m--)
{
k=getint()-1;
x=getint();
while(k--)
{
y=getint();
g[x].push_back(y),g[y].push_back(x);
x=y;
}
}
tarjan(1);
cout<<ans<<'\n';
return 0;
}