2026.1.23 闲话:TopTree 维护仙人掌
贡献一发 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_back
using 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 TopTree
bool 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(_); // Rake
x = 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;
}

浙公网安备 33010602011771号