樹的啓發式合併

樹的啓發式合併能夠解決不少不涉及修改的子樹查詢問題。c++

每次向上合併時,輕鏈併入重鏈,可使得總複雜度由\(O(n^2)\)變成\(O(n\log(n))\)。由於每次加入重鏈,子樹大小都會翻倍。spa

例題:codeforces 600E

給定一棵樹,每一個節點都有一個顏色值。
定義一種顏色值佔領一棵子樹,當且僅當這種顏色是這棵子樹中出現最多的顏色。code

問每一個節點爲根的子樹中,佔領這棵子樹的顏色值之和。it

代碼

#include <bits/stdc++.h>

#define FOPI freopen("in.txt", "r", stdin)
#define FOPO freopen("out.txt", "w", stdout)

using namespace std;
typedef long long LL;
typedef pair<int, int> pr;
const int maxn = 1e5 + 100;

int n;
vector<int> v[maxn];
map<int, int> col[maxn];
LL ans[maxn];
int maxt[maxn];

void merge(int x, int y)
{
    if (col[x].size() < col[y].size()) {
        swap(col[x], col[y]);
        ans[x] = ans[y];
        maxt[x] = maxt[y];
    }

    for (auto val : col[y]) {
        int a = val.first, b = val.second;
        col[x][a] += b;

        if (col[x][a] > maxt[x]) {
            maxt[x] = col[x][a];
            ans[x] = a;
        }
        else if (col[x][a] == maxt[x]) {
            ans[x] += a;
        }
    }
    col[y].clear();
}

void dfs(int x, int from)
{
    for (int y : v[x]) {
        if (y == from) continue;
        dfs(y, x);
        merge(x, y);
    }
}

int main()
{
    //FOPI;

    scanf("%d", &n);
    int x, y;
    for (int i = 1; i <= n; i++) {
        scanf("%d", &x);
        col[i][x] = 1;
        ans[i] = x;
        maxt[i] = 1;
    }

    for (int i = 1; i <= n-1; i++) {
        scanf("%d%d", &x, &y);
        v[x].push_back(y), v[y].push_back(x);
    }

    dfs(1, 0);

    printf("%lld", ans[1]);
    for (int i = 2; i <= n; i++)
        printf(" %lld", ans[i]);
    puts("");
}
相關文章
相關標籤/搜索