题目链接
如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人掌图(cactus)。
所谓简单回路就是指在图上不重复经过任何一个顶点的回路。

举例来说,上面的第一个例子是一张仙人图,而第二个不是——注意到它有三条简单回路:(4,3,2,1,6,5,4)、(7,8,9,10,2,3,7) 以及 (4,3,7,8,9,10,2,1,6,5,4),而 (2,3) 同时出现在前两个的简单回路里。
另外,第三张图也不是仙人图,因为它并不是连通图。
显然,仙人图上的每条边,或者是这张仙人图的桥(bridge),或者在且仅在一个简单回路里,两者必居其一。
定义在图上两点之间的距离为这两点之间最短路径的距离。
定义一个图的直径为这张图相距最远的两个点的距离。
现在我们假定仙人图的每条边的权值都是 1,你的任务是求出给定的仙人图的直径。
输入格式
第一行包括两个整数 n 和 m。其中 n 代表顶点个数,我们约定图中的顶点将从 1 到 n 编号。
接下来一共有 m 行。代表 m 条路径。
每行的开始有一个整数 k,代表在这条路径上的顶点个数。接下来是 k 个 1 到 n 之间的整数,分别对应了一个顶点,相邻的顶点表示存在一条连接这两个顶点的边。
一条路径上可能通过一个顶点好几次,比如对于第一个样例,第一条路径从 3 经过 8,又从 8 返回到了 3,但是我们保证所有的边都会出现在某条路径上,而且不会重复出现在两条路径上,或者在一条路径上出现两次。
输出格式
只需输出一个数,这个数表示仙人图的直径长度。
数据范围
1≤n≤50000,
0≤m≤10000,
2≤k≤1000
输入样例1:
输出样例1:
输入样例2:
输出样例2:
样例解释
对于第一个样例:
如图,6 号点和 12 号点的最短路径长度为 8,所以这张图的直径为 8。

解题思路
仙人掌,单调队列
求解仙人掌的直径
先将仙人掌转换为圆方树,考虑像求树的直径的 dp
算法,在树中,对于一条直径来说,其一定有一个最高点,且其通过最高点分为最长链和次长链两部分,dfs
枚举该最长链,如果最高点是圆点的话,可以发现对于原图,其有一种情况无法处理:当这个最高点在环上时,而这种情况恰好对于最高点是方点的情况,即当最高点是方点时,相当于处理到环的情况,假设 d[i] 为环上一点 i 的最长链,此时要求 d[i]+d[j]+i−j(i≤j,i−j≤环的长度/2)最大,即 d[i]+i+d[j]−j 最大,d[i]+i 固定,即在 环的长度/2 的范围内找 d[j]−j 的最大值,即滑动窗口问题
代码
#include <bits/stdc++.h>
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=100005,M=3*N,inf=1e8;
int n,new_n,m;
int h[2][N],e[M],ne[M],w[M],idx;
int dfn[N],low[N],timestamp;
int fu[N],fw[N],fe[N],s[N],s_tot[N];
int res,f[N],d[N],q[N];
void add(int h[],int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void build_circle(int x,int y,int z)
{
int sum=z;
for(int i=y;i!=x;i=fu[i])
{
s[i]=sum;
sum+=fw[i];
}
s[x]=s_tot[x]=sum;
add(h[1],x,++new_n,0);
for(int i=y;i!=x;i=fu[i])
{
s_tot[i]=sum;
add(h[1],new_n,i,min(s[i],sum-s[i]));
}
}
void tarjan(int x,int from)
{
dfn[x]=low[x]=++timestamp;
for(int i=h[0][x];~i;i=ne[i])
{
int j=e[i];
if(!dfn[j])
{
tarjan(j,i);
fu[j]=x,fw[j]=w[i],fe[j]=i;
low[x]=min(low[x],low[j]);
if(dfn[x]<low[j])add(h[1],x,j,1);
}
else if(i!=(from^1))low[x]=min(low[x],dfn[j]);
}
for(int i=h[0][x];~i;i=ne[i])
{
int j=e[i];
if(dfn[x]<dfn[j]&&fe[j]!=i)build_circle(x,j,w[i]);
}
}
int dfs(int x)
{
int d1=0,d2=0;
for(int i=h[1][x];~i;i=ne[i])
{
int j=e[i];
int t=dfs(j)+w[i];
if(t>=d1)d2=d1,d1=t;
else if(t>d2)d2=t;
}
f[x]=d1;
if(x<=n)res=max(res,d1+d2);
else
{
int sz=0;
d[sz++]=-inf;
for(int i=h[1][x];~i;i=ne[i])d[sz++]=f[e[i]];
for(int i=0;i<sz;i++)d[i+sz]=d[i];
int hh=0,tt=-1;
for(int i=0;i<sz*2;i++)
{
if(hh<=tt&&i-q[hh]>sz/2)hh++;
if(hh<=tt)res=max(res,d[i]+i+d[q[hh]]-q[hh]);
while(hh<=tt&&d[i]-i>=d[q[tt]]-q[tt])tt--;
q[++tt]=i;
}
}
return f[x];
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
while(m--)
{
int k,x,y;
scanf("%d%d",&k,&x);
while(--k)
{
scanf("%d",&y);
add(h[0],x,y,1);
add(h[0],y,x,1);
x=y;
}
}
new_n=n;
tarjan(1,-1);
dfs(1);
printf("%d",res);
return 0;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!