2026.1.23 闲话:TopTree 维护仙人掌

贡献一发 TopTree 维护仙人掌的代码。

不保证绝对正确,要是被 hack 了记得告诉我,我马上改 qwq。

记录即将完工两次被机房电脑自动关机做局。

引入

TopTree 维护树就不多提了,默认大家都会了。

在树上,使用 Compress 和 Rake 便可以将一棵树缩成一条边。

在仙人掌上,由于一条边至多出现在一个环内,而一个环缩起来会产生两个相同的边,此时就需要引入新的操作 Twist 了。

\[x = (a, b, E_x), y = (a, b, E_y) \]

\[Twist(x, y) = (a, b, E_x \cup E_y) \]

这样就可以将一颗仙人掌缩成一条边了。

实现

因为机房的电脑能上的网址几乎找不到 TopTree 维护仙人掌的代码,懒得找了,索性自己写了。

首先想一个很暴力的做法,直接建圆方树,可是这样会合并许多重复的点。

于是考虑只以环为基础进行构建,不过直接将环看做是一个大点会有很大的问题,无法区分一下两种情况,因为其都是两个环相连。

一次 Compress。

两次 Compress。

假设现在将所有环破了后建树,容易发现一个环缩成的簇的上界点一定是这个环内深度最小的点,称这个点为这个环的顶。

接下来观察些性质:

如果两个环有交点,那么这个交点只有可能是其中一个或两个公共的顶。

这样便可以直接由顶连向这个环,然后再连向其他点,这样就可以完美解决上面的问题。

特别的,如果一个顶作为另一个环的非顶存在,那么直接让那个环代替这个点。

第一种情况:

第二种情况:

此时就建出了一颗可以使用的树,为了保证复杂度,所以将其剖了。

假设这个环连到这个环的重儿子的点叫做这个环的底,可以发现最后这个环会缩成一条从顶到底的边。

现在来回顾一下树是怎么建的 TopTree,我们将轻边全都 Rake 到重边上,然后将一条重链整个 Compress 起来。

那扩展到仙人掌也是同理。

假设现在是重链上的一个环,那么去找所有连向这个环上点的边 Rake 到这个环上,然后找到顶到底的两条路径 Compress 起来再 Twist 作为重链边就好了。

这里需要注意特殊处理一下从父亲到这个点的边。

  1. 如果是父亲(或环的顶)的重儿子,那么将这个节点缩起来后把其他边 Rake 到这里。
  2. 如果是轻儿子,则只需要把连向父亲的边一起处理一下就好了。

代码

细节还是很多的。

我的写法可能很唐,建议自己多多手摸一下。

#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;
}
posted @ 2026-01-25 16:11  QEDQEDQED  阅读(1)  评论(0)    收藏  举报