bzoj1023 [SHOI2008]cactus仙人掌图
1023: [SHOI2008]cactus仙人掌图
Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 3174 Solved: 1312
[Submit][Status][Discuss]
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。
分析:比较难的一道题.
考虑dp,设f[i]表示以i为根的子树中,i为端点的最长链的长度.dfs,用经过点u的最长链+次长链更新答案.并用当前链的长度更新最长链的长度.如果遇到环了,就要单独处理.在环上的答案肯定是环上两个点延伸出去的两条链+在环上经过的最短路.即f[x] + f[y] + pos[x] - pos[y].将环变成一段区间,那么转移中y的下标就有了限制,又因为是求最值,所以可以用单调队列优化.
单调队列中维护的东西比较特殊,不仅有f[y],还涉及到pos[y],将f[y]-pos[y]看作一个整体来维护.因为距离的定义是最短路,所以在环上经过的路径一定是较短的那条路径.当i与队首的下标差>len/2时,说明已经不是最短路了,就要将队首提前.最后还要更新一下f[x].
环上求最值可以用单调队列维护要记清楚了.当前点是否在树内或环内的判断标准也要熟记.
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 400010; int n,m,head[maxn],to[maxn],nextt[maxn],tot = 1; int q[maxn],pos[maxn],cnt,a[maxn]; int dfs_clock,pre[maxn],low[maxn],fa[maxn],f[maxn],dist[maxn],ans; void add(int x,int y) { to[tot] = y; nextt[tot] = head[x]; head[x] = tot++; } void solve(int x,int y) { cnt = dist[y] - dist[x] + 1; for (int i = y; i != x; i = fa[i]) a[cnt--] = f[i]; a[cnt] = f[x]; cnt = dist[y] - dist[x] + 1; for (int i = 1; i <= cnt; i++) a[i + cnt] = a[i]; int l = 1,r = 1; pos[1] = 1; for (int i = 2; i <= cnt * 2; i++) { while (l <= r && i - pos[l] > cnt / 2) l++; ans = max(ans,a[pos[l]] + a[i] + i - pos[l]); while (l <= r && a[pos[r]] - pos[r] <= a[i] - i) r--; pos[++r] = i; } for (int i = 2; i <= cnt; i++) f[x] = max(f[x],a[i] + min(i - 1,cnt - i + 1)); } void dfs(int u,int faa) { pre[u] = low[u] = ++dfs_clock; for (int i = head[u];i;i = nextt[i]) { int v = to[i]; if (v == faa) continue; if (!pre[v]) { fa[v] = u; dist[v] = dist[u] + 1; dfs(v,u); low[u] = min(low[u],low[v]); } else low[u] = min(low[u],pre[v]); if (pre[u] < low[v]) { ans = max(ans,f[u] + f[v] + 1); f[u] = max(f[u],f[v] + 1); } } for (int i = head[u];i;i = nextt[i]) { int v = to[i]; if (v == faa) continue; if (fa[v] != u && pre[u] < pre[v]) //此时u是dfs最先进入环中的点 v在u访问其它儿子的时候就被访问到了,所以v是深度最大的点 solve(u,v); } } int main() { scanf("%d%d",&n,&m); for (int i = 1; i <= m; i++) { int num,last; scanf("%d",&num); scanf("%d",&last); for (int i = 1; i < num; i++) { int t; scanf("%d",&t); add(last,t); add(t,last); last = t; } } dfs(1,0); printf("%d\n",ans); return 0; }