Lomsat gelral(樹上啓發式合併/動態開點與線段樹合併)

You are given a rooted tree with root in vertex 1. Each vertex is coloured in some colour.

Let’s call colour c dominating in the subtree of vertex v if there are no other colours that appear in the subtree of vertex v more times than colour c. So it’s possible that two or more colours will be dominating in the subtree of some vertex.

The subtree of vertex v is the vertex v and all other vertices that contains vertex v in each path to the root.

For each vertex v find the sum of all dominating colours in the subtree of vertex v.

Input
The first line contains integer n (1 ≤ n ≤ 105) — the number of vertices in the tree.

The second line contains n integers c i (1 ≤ c i ≤ n), c i — the colour of the i-th vertex.

Each of the next n - 1 lines contains two integers x j, y j (1 ≤ x j, y j ≤ n) — the edge of the tree. The first vertex is the root of the tree.

Output
Print n integers — the sums of dominating colours for each vertex.

Examples

input
4
1 2 3 4
1 2
2 3
2 4
output
10 9 3 4
input
15
1 2 3 1 2 3 3 1 1 3 2 2 1 2 3
1 2
1 3
1 4
1 14
1 15
2 5
2 6
2 7
3 8
3 9
3 10
4 11
4 12
4 13
output
6 5 4 3 2 3 3 1 1 3 2 2 1 2 3

學樹上啓發式合併必做的一道題。
對於該問題,如果採用暴力的話有兩種方法,一種是開闢一個統計子樹各種顏色個數的數組,對於每棵子樹,遍歷子樹上的節點,用該數組統計得到答案然後清空數組,計算另一棵子樹的答案,複雜度爲O(n2)O(n^2)。另一種做法是樹形dp(假設內存不受限),對於每個節點開闢一個統計子樹各種顏色個數的數組,狀態轉移的複雜度爲O(n)O(n),總的複雜度同樣爲O(n2)O(n^2)
通過觀察發現:方法一遍歷每個節點更新答案的複雜度爲O(1)O(1),但是每個點需要遍歷多次。而方法二中每個點只需計算一次,但是計算一個點的複雜度爲O(n)O(n)
樹上啓發式合併其實就是結合這兩種方法的優點的一種優雅的暴力算法。對於節點較多的子樹,可以採取方法二,其餘節點較少的子樹採取方法一。由於對於每個節點的子樹,只有一棵節點數最多的子樹採取方法二,因此可以避免因方法二中將兩個節點合併而浪費時間,直接利用這棵子樹的狀態。然後對於剩餘的子樹以以及該節點,由於節點數已經降到最低,因此耗時將至最低。該算法的複雜度爲O(nlogn)O(nlogn)

#include<bits/stdc++.h>

#define si(a) scanf("%d",&a)
#define sl(a) scanf("%lld",&a)
#define sd(a) scanf("%lf",&a)
#define sc(a) scahf("%c",&a);
#define ss(a) scanf("%s",a)
#define pi(a) printf("%d\n",a)
#define pl(a) printf("%lld\n",a)
#define pc(a) putchar(a)
#define ms(a) memset(a,0,sizeof(a))
#define repi(i, a, b) for(register int i=a;i<=b;++i)
#define repd(i, a, b) for(register int i=a;i>=b;--i)
#define reps(s) for(register int i=head[s];i;i=Next[i])
#define ll long long
#define vi vector<int>
#define pii pair<int,int>
#define mii unordered_map<int,int>
#define msi unordered_map<string,int>
#define lowbit(x) ((x)&(-(x)))
#define ce(i, r) i==r?'\n':' '
#define pb push_back
#define fi first
#define se second
#define INF 0x3f3f3f3f
#define pr(x) cout<<#x<<": "<<x<<endl
using namespace std;

inline int qr() {
    int f = 0, fu = 1;
    char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-')fu = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        f = (f << 3) + (f << 1) + c - 48;
        c = getchar();
    }
    return f * fu;
}

const int N = 1e5 + 10;
int head[N], ver[N << 1], Next[N << 1], tot;
int b[N], a[N], n;
int son[N], dfn[N], s[N], c[N], num, mx;
ll ans[N], now;

inline void add(int x, int y) {
    ver[++tot] = y;
    Next[tot] = head[x];
    head[x] = tot;
}

inline void read() {
    n = qr();
    repi(i, 1, n)b[i] = qr();
    repi(i, 1, n - 1) {
        int x = qr(), y = qr();
        add(x, y), add(y, x);
    }
}

void dfs(int x, int f) {
    s[x] = 1, dfn[x] = ++num;
    reps(x) {
        int y = ver[i];
        if (y == f)continue;
        dfs(y, x), s[x] += s[y];
        son[x] = s[y] > s[son[x]] ? y : son[x];
    }
}

inline void add(int x) {
    c[x]++;
    if (c[x] > mx)mx = c[x], now = x;
    else if (c[x] == mx)now += x;
}

void dfs(int x, int f, bool k) {
    reps(x) {
        int y = ver[i];
        if (y != f && y != son[x])dfs(y, x, false);
    }
    if (son[x])dfs(son[x], x, true);
    reps(x) {
        int y = ver[i];
        if (y != f && y != son[x])repi(i, dfn[y], dfn[y] + s[y] - 1)add(a[i]);
    }
    add(a[dfn[x]]), ans[x] += now;
    if (!k) {
        mx = now = 0;
        repi(i, dfn[x], dfn[x] + s[x] - 1)c[a[i]] = 0;
    }
}

int main() {
    read();
    dfs(1, 0);
    repi(i, 1, n)a[dfn[i]] = b[i];
    dfs(1, 0, true);
    repi(i, 1, n)printf("%lld%c", ans[i], ce(i, n));
    return 0;
}

另外,暴力解法二中狀態合並可以用線段樹合併優化到O(logn)O(logn),線段樹動態開點,類似於雨天的尾巴。因此時間複雜度爲O(nlogn)O(nlogn)

#include<bits/stdc++.h>

#define si(a) scanf("%d",&a)
#define sl(a) scanf("%lld",&a)
#define sd(a) scanf("%lf",&a)
#define sc(a) scahf("%c",&a);
#define ss(a) scanf("%s",a)
#define pi(a) printf("%d\n",a)
#define pl(a) printf("%lld\n",a)
#define pc(a) putchar(a)
#define ms(a) memset(a,0,sizeof(a))
#define repi(i, a, b) for(register int i=a;i<=b;++i)
#define repd(i, a, b) for(register int i=a;i>=b;--i)
#define reps(s) for(register int i=head[s];i;i=Next[i])
#define ll long long
#define vi vector<int>
#define pii pair<int,int>
#define mii unordered_map<int,int>
#define msi unordered_map<string,int>
#define lowbit(x) ((x)&(-(x)))
#define ce(i, r) i==r?'\n':' '
#define pb push_back
#define fi first
#define se second
#define INF 0x3f3f3f3f
#define pr(x) cout<<#x<<": "<<x<<endl
using namespace std;

inline int qr() {
    int f = 0, fu = 1;
    char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-')fu = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        f = (f << 3) + (f << 1) + c - 48;
        c = getchar();
    }
    return f * fu;
}

const int N = 1e5 + 10;
int head[N], ver[N << 1], Next[N << 1], tot;
int n;

struct Tree {
    struct T {
        int lc, rc;
        int dat;
        ll res;
    } tr[N * 60];
    int tot;

    inline void init() {
        tot = 0;
    }

    inline int build() {
        tot++;
        tr[tot].lc = tr[tot].rc = tr[tot].dat = 0;
        return tot;
    }

    inline void insert(int p, int l, int r, int val, int delta) {
        if (l == r) {
            tr[p].dat += delta;
            tr[p].res = val;
            return;
        }
        int mid = (l + r) >> 1;
        if (val <= mid) {
            if (!tr[p].lc) tr[p].lc = build();
            insert(tr[p].lc, l, mid, val, delta);
        } else {
            if (!tr[p].rc) tr[p].rc = build();
            insert(tr[p].rc, mid + 1, r, val, delta);
        }
        if (tr[tr[p].lc].dat > tr[tr[p].rc].dat)tr[p].dat = tr[tr[p].lc].dat, tr[p].res = tr[tr[p].lc].res;
        else if (tr[tr[p].lc].dat < tr[tr[p].rc].dat)tr[p].dat = tr[tr[p].rc].dat, tr[p].res = tr[tr[p].rc].res;
        else tr[p].dat = tr[tr[p].lc].dat, tr[p].res = tr[tr[p].lc].res + tr[tr[p].rc].res;
    }

    int merge(int p, int q, int l, int r) {
        if (!p) return q;
        if (!q) return p;
        if (l == r) {
            tr[p].dat += tr[q].dat;
            return p;
        }
        int mid = (l + r) >> 1;
        tr[p].lc = merge(tr[p].lc, tr[q].lc, l, mid);
        tr[p].rc = merge(tr[p].rc, tr[q].rc, mid + 1, r);
        if (tr[tr[p].lc].dat > tr[tr[p].rc].dat)tr[p].dat = tr[tr[p].lc].dat, tr[p].res = tr[tr[p].lc].res;
        else if (tr[tr[p].lc].dat < tr[tr[p].rc].dat)tr[p].dat = tr[tr[p].rc].dat, tr[p].res = tr[tr[p].rc].res;
        else tr[p].dat = tr[tr[p].lc].dat, tr[p].res = tr[tr[p].lc].res + tr[tr[p].rc].res;
        return p;
    }
};

Tree tr;

inline void add(int x, int y) {
    ver[++tot] = y;
    Next[tot] = head[x];
    head[x] = tot;
}

inline void read() {
    n = qr();
    repi(i, 1, n)tr.build();
    repi(i, 1, n)tr.insert(i, 1, n, qr(), 1);
    repi(i, 1, n - 1) {
        int x = qr(), y = qr();
        add(x, y), add(y, x);
    }
}

void dp(int x, int f) {
    reps(x) {
        int y = ver[i];
        if (y == f)continue;
        dp(y, x);
        tr.merge(x, y, 1, n);
    }
}

int main() {
    read();
    dp(1, 0);
    repi(i, 1, n)printf("%lld%c", tr.tr[i].res, ce(i, n));
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章