2752. 仙人掌图
题目链接
2752. 仙人掌图
如果某个无向连通图的任意一条边至多只出现在一条简单回路(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 \le n \le 50000\),
\(0 \le m \le 10000\),
\(2 \le k \le 1000\)
输入样例1:
15 3
9 1 2 3 4 5 6 7 8 3
7 2 9 10 11 12 13 10
5 2 14 9 15 10
输出样例1:
8
输入样例2:
10 1
10 1 2 3 4 5 6 7 8 9 10
输出样例2:
9
样例解释
对于第一个样例:
如图,\(6\) 号点和 \(12\) 号点的最短路径长度为 \(8\),所以这张图的直径为 \(8\)。
解题思路
仙人掌,单调队列
求解仙人掌的直径
先将仙人掌转换为圆方树,考虑像求树的直径的 dp
算法,在树中,对于一条直径来说,其一定有一个最高点,且其通过最高点分为最长链和次长链两部分,dfs
枚举该最长链,如果最高点是圆点的话,可以发现对于原图,其有一种情况无法处理:当这个最高点在环上时,而这种情况恰好对于最高点是方点的情况,即当最高点是方点时,相当于处理到环的情况,假设 \(d[i]\) 为环上一点 \(i\) 的最长链,此时要求 \(d[i]+d[j]+i-j\)(\(i\le j,i-j\leq {环的长度}/2\))最大,即 \(d[i]+i+d[j]-j\) 最大,\(d[i]+i\) 固定,即在 \({环的长度}/2\) 的范围内找 \(d[j]-j\) 的最大值,即滑动窗口问题
- 时间复杂度:\(O(n+m)\)
代码
// Problem: 仙人掌图
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/description/2754/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#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;
}