贡献一发 TopTree 维护仙人掌的代码。
不保证绝对正确,要是被 hack 了记得告诉我,我马上改 qwq。
记录即将完工两次被机房电脑自动关机做局。
引入
TopTree 维护树就不多提了,默认大家都会了。
在树上,使用 Compress 和 Rake 便可以将一棵树缩成一条边。
在仙人掌上,由于一条边至多出现在一个环内,而一个环缩起来会产生两个相同的边,此时就需要引入新的操作 Twist 了。
这样就可以将一颗仙人掌缩成一条边了。
实现
因为机房的电脑能上的网址几乎找不到 TopTree 维护仙人掌的代码,懒得找了,索性自己写了。
首先想一个很暴力的做法,直接建圆方树,可是这样会合并许多重复的点。
于是考虑只以环为基础进行构建,不过直接将环看做是一个大点会有很大的问题,无法区分一下两种情况,因为其都是两个环相连。

一次 Compress。

两次 Compress。
假设现在将所有环破了后建树,容易发现一个环缩成的簇的上界点一定是这个环内深度最小的点,称这个点为这个环的顶。
接下来观察些性质:
如果两个环有交点,那么这个交点只有可能是其中一个或两个公共的顶。
这样便可以直接由顶连向这个环,然后再连向其他点,这样就可以完美解决上面的问题。
特别的,如果一个顶作为另一个环的非顶存在,那么直接让那个环代替这个点。
第一种情况:

第二种情况:

此时就建出了一颗可以使用的树,为了保证复杂度,所以将其剖了。
假设这个环连到这个环的重儿子的点叫做这个环的底,可以发现最后这个环会缩成一条从顶到底的边。
现在来回顾一下树是怎么建的 TopTree,我们将轻边全都 Rake 到重边上,然后将一条重链整个 Compress 起来。
那扩展到仙人掌也是同理。
假设现在是重链上的一个环,那么去找所有连向这个环上点的边 Rake 到这个环上,然后找到顶到底的两条路径 Compress 起来再 Twist 作为重链边就好了。
这里需要注意特殊处理一下从父亲到这个点的边。
- 如果是父亲(或环的顶)的重儿子,那么将这个节点缩起来后把其他边 Rake 到这里。
- 如果是轻儿子,则只需要把连向父亲的边一起处理一下就好了。
代码
细节还是很多的。
我的写法可能很唐,建议自己多多手摸一下。
#include <iostream>
#include <cstring>
#include <vector>
#include <map>using namespace std;const int N = 2e5 + 10;#define emp emplace_backusing pii = pair<int, int>;int tot, f[N], son[N], siz[N], dep[N], m;
vector <int> G[N], EG[N];// Begin TopTree
struct Node
{int f[2][2];int ls, rs, fa, tag, siz;
}tr[N << 1];Node Rake(int x, int y)
{Node tmp;tmp.ls = x, tmp.rs = y, tmp.tag = 0;for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) tmp.f[i][j] = -0x3f3f3f3f;for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) for (int k = 0; k < 2; k++) tmp.f[i][j] = max(tmp.f[i][j], tr[x].f[i][j] + tr[y].f[i][k]);return tmp;
}Node Compress(int x, int y)
{Node tmp;tmp.ls = x, tmp.rs = y, tmp.tag = 1;for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) tmp.f[i][j] = -0x3f3f3f3f;for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) for (int k = 0; k < 2; k++) tmp.f[i][j] = max(tmp.f[i][j], tr[x].f[i][k] + tr[y].f[k][j]);return tmp;
}Node Twist(int x, int y)
{Node tmp;tmp.ls = x, tmp.rs = y, tmp.tag = 2;for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) tmp.f[i][j] = -0x3f3f3f3f;for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) tmp.f[i][j] = max(tmp.f[i][j], tr[x].f[i][j] + tr[y].f[i][j] - j);return tmp;
}void PushUp(int x)
{int _fa = tr[x].fa;if (tr[x].tag == 0) tr[x] = Rake(tr[x].ls, tr[x].rs);else if (tr[x].tag == 1) tr[x] = Compress(tr[x].ls, tr[x].rs);else tr[x] = Twist(tr[x].ls, tr[x].rs);tr[tr[x].ls].fa = x, tr[tr[x].rs].fa = x;tr[x].siz = tr[tr[x].ls].siz + tr[tr[x].rs].siz;tr[x].fa = _fa;
}int Merge(int x, int y, int tag)
{tr[++tot].ls = x, tr[tot].rs = y, tr[tot].tag = tag;PushUp(tot);return tot;
}Node New()
{Node tmp;tmp.tag = 0, tmp.ls = tmp.rs = tmp.fa = 0;tmp.siz = 1;tmp.f[0][0] = 0, tmp.f[1][0] = 0, tmp.f[0][1] = 1, tmp.f[1][1] = -0x3f3f3f3f;return tmp;
}int Solve(vector<int> &p, int l, int r, int tag)
{if (l == r) return p[l];int mid = r - 1, s = 0, ns = 0;for (int i = l; i <= r; i++) s += tr[p[i]].siz;for (int i = l; i < r; i++){ns += tr[p[i]].siz;if ((ns << 1) >= s) {mid = i; break;}}return Merge(Solve(p, l, mid, tag), Solve(p, mid + 1, r, tag), tag);
}int DEP;
void D(int x, int dep)
{if (!tr[x].ls) {DEP = max(DEP, dep); return ;}D(tr[x].ls, dep + 1), D(tr[x].rs, dep + 1);
}
// End TopTreebool vis[N];
vector <int> EA[N], EB[N];
int Etop[N], Eson[N], Etot, n, be[N];int Build(int x);// 检查 y 是否处在 x 这个环内
bool CheckIN(int y, int x)
{return (be[y] == x || y == Etop[x]);
}// 标记点之间的边编号
map <int, int> MAP[N];// 对一个环的一条路径处理
int CalcLoop(int x, vector<int> &E)
{vector<int> q;for (int w = 0; w < (int)E.size() - 1; w++){int to = E[w];vector <int> t;t.emp(MAP[E[w]][E[w + 1]]);if (w){for (auto j : G[to]){if (CheckIN(j, x) || CheckIN(j, son[x]) || j == f[to]) continue;int P = Build(be[j]);if (P) t.emp(P);}}int _w = 0;if (t.size()) _w = Solve(t, 0, t.size() - 1, 0);if (_w) q.emp(_w); // Rake}return Solve(q, 0, q.size() - 1, 1); // Compress
}// 对一个节点处理
int Calc(int x)
{vector<int> q;if (x > n){// 合并两条从顶到底的路径q.emp(Merge(CalcLoop(x, EA[x]), CalcLoop(x, EB[x]), 2));// 不是父亲的重儿子if (x != son[be[Etop[x]]]) return q[0];// 将其他边 Rake 到这里for (auto to : G[Etop[x]]){if (be[to] == x || to == f[Etop[x]] || be[to] == son[Etop[x]]) continue;if (CheckIN(to, be[Etop[x]])) continue;int P = Build(be[to]);if (P) q.emp(P);}return Solve(q, 0, q.size() - 1, 0);}else{if (x && x == son[be[f[x]]]){// 处理到父亲的边q.emp(MAP[f[x]][x]);for (auto to : G[f[x]]){if (to == x || to == f[f[x]] || CheckIN(to, be[f[x]])) continue;int P = Build(be[to]);if (P) q.emp(P);}if (!q.size()) return 0;else return Solve(q, 0, q.size() - 1, 0);}return 0;}
}// 对一条重链处理
int Build(int x)
{if (vis[x]) return 0;vis[x] = 1;vector<int> p;// 由于缩完之后需要和上面的点连接起来,于是需要包含到父亲的边if (f[x] && x == Etop[be[x]]) p.emp(MAP[x][f[x]]);do{int _ = Calc(x);if (_) p.emp(_); // Rakex = son[x];}while (x);return Solve(p, 0, p.size() - 1, 1); // Compress
}// 找环
void dfs(int x, int fa)
{vis[x] = 1, f[x] = fa, dep[x] = dep[fa] + 1;for (auto to : G[x]){if (to == fa) continue;if (vis[to]){if (dep[to] <= dep[x]){int w = x; ++Etot;while (w != to) EA[Etot].emp(w), be[w] = Etot, w = f[w];EA[Etot].emp(to), Etop[Etot] = to;}continue;}dfs(to, x);}
}void DFS(int x, int fa = 0)
{vis[x] = 1;if (x) siz[x] = EA[x].size() + 1;dep[x] = dep[fa] + 1;for (auto to : EG[x]){if (to == fa || vis[to]) continue;DFS(to, x);if (x) siz[x] += siz[to];if (siz[to] > siz[son[x]]) son[x] = to;}
}signed main()
{ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);cin >> n >> m; Etot = n;for (int i = 1, x, y; i <= m; i++) cin >> x >> y, G[x].emp(y), G[y].emp(x), MAP[x][y] = MAP[y][x] = i, tr[i] = New();for (int i = 1; i <= n; i++) be[i] = Etop[i] = i;G[0].emp(1), MAP[0][1] = MAP[1][0] = ++m;tr[tot = m] = New();dfs(0, 0), memset(vis, 0, sizeof(vis));for (int i = 0; i <= n; i++) for (auto to : G[i]) if (!CheckIN(to, be[i])) EG[be[i]].emp(be[to]);DFS(0), memset(vis, 0, sizeof(vis));for (int i = 0; i <= n; i++) for (auto to : G[i]) if (be[to] == son[be[i]]) Eson[be[i]] = i;for (int i = n + 1; i <= Etot; i++){// 顶到底的两条路径vector<int> E = EA[i]; EA[i].clear();if (!Eson[i]) Eson[i] = E[E.size() - 2];for (int j = (int)E.size() - 1; ~j; j--){EA[i].emp(E[j]);if (E[j] == Eson[i]){EB[i].emp(E[E.size() - 1]);for (int k = 0; k <= j; k++) EB[i].emp(E[k]);break;}}}int rt = Build(0);cout << max(tr[rt].f[0][0], tr[rt].f[0][1]) << '\n';D(rt, 0);cerr << DEP;return 0;
}