图论-树上启发式合并(DSU On Tree)

树上启发式合并(DSU On Tree)详解
本文介绍了来自 Codeforces 的“树上启发式合并”(DSU On Tree),它是不带修改的有根树子树信息统计问题的常规操作。通过轻重链剖分思想,可将问题复杂度降为 \(O(n \cdot log\ n)\)。还举例说明不同子树信息统计问题的解决方法,如求众数和求权值大于根节点的点的个数。

Disjoint Set Union On Tree ,似乎是来自 Codeforces 的一种新操作,似乎被叫做“树上启发式合并”。
不带修改的有根树子树信息统计问题中,似乎树上莫队和这个 DSU On Tree 是两类常规操作。
先对树按轻重链剖分。对于每个节点,先计算轻儿子为根的子树信息,每次计算后消除影响,再去计算其他轻儿子。然后计算重儿子为根的子树信息,不消除影响,并把轻儿子们为根的子树信息加入,再合并这个节点本身的信息。由于一个大小 \(x\) 的子树被消除影响后,都把信息合并到了一个大于等于 \(2x+1\) 的子树,如此递归下去,它显然至多被消除影响 \(log\ n\) 次。利用轻重链剖分的思想,就把这个问题 \(O(n \cdot log\ n)\) 解决了(假设合并信息是 \(O(1)\) 的)。

一道求子树内众数(记在 ans 里)的题的代码:

#include <stdio.h>
#include <vector>

using namespace std;

const int _N = 160000;

vector<int> G[_N];
int hvs[_N], siz[_N], cnt[_N], A[_N], ans[_N], mx;

void connect(int p, int dad)
{
    siz[p] = 1;
    for (int i = G[p].size() - 1; i >= 0; --i) {
        int v = G[p][i];
        if (v == dad) continue;
        connect(v, p);
        siz[p] += siz[v];
        if (!hvs[p] || siz[hvs[p]] < siz[v]) hvs[p] = v;
    }
    return;
}

void clear(int p, int dad)
{
    --cnt[A[p]];
    for (int i = G[p].size() - 1; i >= 0; --i) {
        int v = G[p][i];
        if (v == dad) continue;
        clear(v, p);
    }
    return;
}

void insert(int p, int dad)
{
    mx = max(mx, ++cnt[A[p]]);
    for (int i = G[p].size() - 1; i >= 0; --i) {
        int v = G[p][i];
        if (v == dad) continue;
        insert(v, p);
    }
    return;
}

void dfs(int p, int dad)
{
    for (int i = G[p].size() - 1; i >= 0; --i) {
        int v = G[p][i];
        if (v == dad || v == hvs[p]) continue;
        dfs(v, p);
        clear(v, p);
        mx = 0;
    }
    if (hvs[p]) dfs(hvs[p], p);
    for (int i = G[p].size() - 1; i >= 0; --i) {
        int v = G[p][i];
        if (v == dad || v == hvs[p]) continue;
        insert(v, p);
    }
    ans[p] = mx = max(mx, ++cnt[A[p]]);
    return;
}

int main()
{
    int N;
    scanf("%d", &N);
    for (int i = 1; i <= N; ++i)
        scanf("%d", &A[i]);
    for (int a, b, i = 1 ; i < N; ++i) {
        scanf("%d%d", &a, &b);
        G[a].push_back(b), G[b].push_back(a);
    }
    connect(1, 0);
    dfs(1, 0);
    for (int i = 1; i <= N; ++i)
        printf("%d ", siz[i] - ans[i]);
    return 0;
}

CF 上原博客 [Tutorial] Sack (dsu on tree)
模板题 CF600E 子树众数统计

Update :关于子树信息统计
有的问题不一定需要 dsu 解决。比如对于“求子树内权值大于子树根节点的点的个数”这个问题,问题具有可减性(这是我口胡的),即:子树内贡献 = 统计子树后信息 - 统计子树前的信息 。所以这个问题用权值树状数组维护,将统计前后的答案作差就可以了,不用 dsu 。求众数却不能用前后答案作差解决,因为作差的过程是 \(O(n)\) 的。感性地说,求众数时子树信息是分裂的,而求例子中的问题时,信息直接对答案贡献 +1 (表示有一个点权值大于根节点权值),可以合并或者说累加。

转载于:https://www.cnblogs.com/ghcred/p/9544170.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值