bzoj1023 [SHOI2008]cactus仙人掌图
Description
如果某个无向连通图的任意一条边至多只出现在一条简单回路(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,你的任务是求出给定的仙人图的直径。
Input
输入的第一行包括两个整数n和m(1≤n≤50000以及0≤m≤10000)。其中n代表顶点个数,我们约定图中的顶点将从1到n编号。接下来一共有m行。代表m条路径。每行的开始有一个整数k(2≤k≤1000),代表在这条路径上的顶点个数。接下来是k个1到n之间的整数,分别对应了一个顶点,相邻的顶点表示存在一条连接这两个顶点的边。一条路径上可能通过一个顶点好几次,比如对于第一个样例,第一条路径从3经过8,又从8返回到了3,但是我们保证所有的边都会出现在某条路径上,而且不会重复出现在两条路径上,或者在一条路径上出现两次。
Output
只需输出一个数,这个数表示仙人图的直径长度。
Sample Input
9 1 2 3 4 5 6 7 8 3
7 2 9 10 11 12 13 10
5 2 14 9 15 10 8
10 1
10 1 2 3 4 5 6 7 8 9 10
Sample Output
9
HINT
对第一个样例的说明:如图,6号点和12号点的最短路径长度为8,所以这张图的直径为8。
正解:仙人掌+$tarjan$+单调队列。
这题是仙人掌入门题,然而我看了好久的题解才看懂。。
首先要明确一点,树的直径算法,也就是两遍$bfs$的做法是错的,这个在$2015$年集训队论文集中$wys$的论文里有指出。
然后这是棵仙人掌,我们考虑一下如何求解。
首先我们还是和树形$dp$的做法一样。先遍历一遍整棵仙人掌,搞清楚仙人掌的结构是怎样的。
当我们遍历的结点不在环内时,直接按照树形$dp$的做法,用最长链和次长链更新$ans$就行了。
当我们遍历的结点在环内时,我们直接把环抠出来,另外处理。
然后我们可以把环拉成一条链,也就是把环倍长。
我们用这个环更新答案时,要考虑一种情况,就是环上两个点的最短距离和两个点子树内的最长链,也就是$f[i]$相加。
这个情况很不好处理,但是我们可以发现,把环拉成序列以后,我们要求的就是在序列中距离在序列长度的$\frac{1}{4}$以内(因为环已经倍长辣)的最优解。
我们把环拉成序列以后,因为扫描序列是顺时针操作的,所以序列中相邻的点在环中也是相邻的,所以两点之间的距离就是它们在序列中的下标差。
于是我们的$ans$就是$max(f[u]+f[v]+u-v)$($idu>idv$且$idu-idv<\frac{1}{4}len$,$len$为序列长度)。于是我们发现这个东西可以用单调队列做。
我们更新完答案以后,再顺便更新一下环顶的$f$值,因为其他的$f$值以后都不会再用到了,我们只要更新环顶的$f$就能做到不影响答案了。
于是这道题就被我们完美地解决了。
1 //It is made by wfj_2048~ 2 #include <algorithm> 3 #include <iostream> 4 #include <complex> 5 #include <cstring> 6 #include <cstdlib> 7 #include <cstdio> 8 #include <vector> 9 #include <cmath> 10 #include <queue> 11 #include <stack> 12 #include <map> 13 #include <set> 14 #define inf (1<<30) 15 #define N (200010) 16 #define il inline 17 #define RG register 18 #define ll long long 19 #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout) 20 21 using namespace std; 22 23 struct edge{ int nt,to; }g[1000010]; 24 25 int head[N],fa[N],dep[N],dfn[N],low[N],f[N],a[N],q[N],n,m,num,cnt,ans; 26 27 il int gi(){ 28 RG int x=0,q=1; RG char ch=getchar(); 29 while ((ch<'0' || ch>'9') && ch!='-') ch=getchar(); 30 if (ch=='-') q=-1,ch=getchar(); 31 while (ch>='0' && ch<='9') x=x*10+ch-48,ch=getchar(); 32 return q*x; 33 } 34 35 il void insert(RG int from,RG int to){ 36 g[++num]=(edge){head[from],to},head[from]=num; return; 37 } 38 39 il void circle(RG int rt,RG int x){ //环上dp 40 RG int top=dep[x]-dep[rt]+1; a[1]=f[rt]; 41 for (RG int i=x;i!=rt;i=fa[i]) a[top--]=f[i]; 42 RG int h=1,t=1; top=dep[x]-dep[rt]+1,q[1]=1; 43 for (RG int i=1;i<=top;++i) a[i+top]=a[i]; //环倍长 44 for (RG int i=2;i<=(top<<1);++i){ 45 while (h<t && i-q[h]>(top>>1)) h++; 46 ans=max(ans,a[i]+a[q[h]]+i-q[h]); 47 while (h<=t && a[q[t]]-q[t]<=a[i]-i) t--; 48 q[++t]=i; 49 } 50 for (RG int i=2;i<=top;++i) //更新环顶结点的f值 51 f[rt]=max(f[rt],a[i]+min(i-1,top-i+1)); 52 return; 53 } 54 55 il void dfs(RG int x,RG int p){ 56 fa[x]=p,dep[x]=dep[p]+1; 57 dfn[x]=low[x]=++cnt; RG int v; 58 for (RG int i=head[x];i;i=g[i].nt){ 59 v=g[i].to; if (v==p) continue; 60 if (!dfn[v]) dfs(v,x),low[x]=min(low[x],low[v]); 61 else low[x]=min(low[x],dfn[v]); 62 if (dfn[x]<low[v]){ //没有形成环的情况 63 ans=max(ans,f[x]+f[v]+1); 64 f[x]=max(f[x],f[v]+1); 65 } 66 } 67 for (RG int i=head[x];i;i=g[i].nt){ //判断是否有环 68 v=g[i].to; if (v==p) continue; 69 if (fa[v]!=x && dfn[x]<dfn[v]) circle(x,v); 70 } 71 return; 72 } 73 74 il void work(){ 75 n=gi(),m=gi(); 76 for (RG int i=1,k,x,v;i<=m;++i){ 77 k=gi(),x=gi(); 78 for (RG int j=1;j<k;++j) 79 v=gi(),insert(x,v),insert(v,x),x=v; 80 } 81 dfs(1,0); printf("%d\n",ans); return; 82 } 83 84 int main(){ 85 File("cactus"); 86 work(); 87 return 0; 88 }