Luogu 4244 [SHOI2008]仙人掌图
BZOJ 1023
如果我们把所有的环都缩成一个点,那么整张图就变成了一棵树,我们可以直接$dp$算出树的直径。
设$f_x$表示$x$的子树中最长链的长度,那么对于$x$的每一个儿子$y$,先用$f_x + f_y + 1$更新答案,再用$f_y + 1$更新$f_x$。
考虑加入环的情况,保留这个$f_x$的设定。我们可以按照搜索顺序把环上第一个搜到的点看成环的“根”,然后用这个“根”来计算这个环。
假设有环$1, 2, 3, ..., m$,$1$是环的“根”,那么我们可以用$f_i + f_j + min(j - i, m - (j - i))\ (i < j)$来更新答案,然后用$max(f_i + min(i - 1, m - (i - 1)))$来更新$f_1$。
发现这个$min$不怎么好更新,可以断环成链复制一倍,然后用单调队列滑动一个长度为$\left \lfloor \frac{m}{2} \right \rfloor$的区间即可。
在$dfs$的时候保留的$tarjan$时候采用的$dfn$和$low$数组,当$dfn_x < low_y$的时候说明走了一条桥边,按照原来的树形$dp$更新答案。
处理完所有子树之后重新扫一遍儿子,观察是否有$fa_y \neq x$并且$dfn_y > dfn_x$的点,如果有,那么$x$是环的“根”,$y$是环的另一个端点。
时间复杂度应该是$O(n)$吧。
Code:
#include <cstdio> #include <cstring> using namespace std; const int N = 5e4 + 5; const int M = 2e5 + 5; int n, m, tot = 0, head[N], f[N], g[N << 1], q[N << 1]; int ans = 0, dfsc = 0, fa[N], dfn[N], low[N], dep[N]; struct Edge { int to, nxt; } e[M]; inline void add(int from, int to) { e[++tot].to = to; e[tot].nxt = head[from]; head[from] = tot; } inline void read(int &X) { X = 0; char ch = 0; int op = 1; for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } inline void chkMax(int &x, int y) { if(y > x) x = y; } inline int min(int x, int y) { return x > y ? y : x; } inline void swap(int &x, int &y) { int t = x; x = y; y = t; } inline void solve(int x, int y) { int cnt = 0; for(int tmp = y; tmp != x; tmp = fa[tmp]) g[++cnt] = f[tmp]; g[++cnt] = f[x]; for(int i = 1; i <= cnt / 2; i++) swap(g[i], g[cnt - i + 1]); /* int cnt = dep[y] - dep[x] + 1; for(int tmp = y; tmp != x; tmp = fa[tmp]) g[cnt--] = f[tmp]; g[cnt] = f[x]; cnt = dep[y] - dep[x] + 1; */ for(int i = 1; i < cnt; i++) g[i + cnt] = g[i]; int l = 1, r = 0; for(int i = 1; i < 2 * cnt; i++) { for(; l <= r && i - q[l] > (cnt / 2); ++l); if(l <= r) chkMax(ans, g[i] + g[q[l]] + i - q[l]); for(; l <= r && g[q[r]] - q[r] <= g[i] - i; --r); q[++r] = i; } for(int i = 2; i <= cnt; i++) chkMax(f[x], g[i] + min(i - 1, cnt - (i - 1))); } void dfs(int x, int fat, int depth) { fa[x] = fat, dep[x] = depth; low[x] = dfn[x] = ++dfsc; for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; if(y == fat) continue; if(!dfn[y]) { dfs(y, x, depth + 1); low[x] = min(low[x], low[y]); } else low[x] = min(low[x], dfn[y]); if(low[y] > dfn[x]) { chkMax(ans, f[x] + f[y] + 1); chkMax(f[x], f[y] + 1); } } for(int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; // if(y == fat) continue; if(fa[y] != x && dfn[y] > dfn[x]) solve(x, y); } } int main() { read(n), read(m); for(int k, i = 1; i <= m; i++) { read(k); for(int lst, now, j = 1; j <= k; j++) { read(now); if(j != 1) add(now, lst), add(lst, now); lst = now; } } dfs(1, 0, 1); printf("%d\n", ans); return 0; }