[luoguP4556] [Vani有约会] 雨天的尾巴

题意

给定 \(n\) 个点的无根树,进行 \(m\) 次操作,每次使 \(x\to y\) 路径上的每个点的可重集合内都插入一个 \(z\),求每个点的可重集合内最多的数是多少,数量相同输出最小的。

sol

路径操作、离线,因此可以想到树上差分。
开一个数组,记录每个点的可重集合内每个数的个数,然后做树上差分。最后求和之后统计即可,但是这样做求和后统计是 \(O(n\max z)\) 的。
考虑在一遍做树上差分时一边计算,这样最后可以一边求和一边记录答案了。可以将每个点上的数组变为动态开点线段树,这样就可以一遍计算一遍做树上差分了,但需要在求和时合并线段树

线段树合并

与 FHQ-Treap 的合并操作类似(见[lnsyoj285/luoguP2596/ZJOI2006]书架)。
如果存在一个位置只有一棵树存在,那么直接把这个点连过去,否则就向下遍历。当遍历到叶子节点时,将这两个节点合并,再将数据上推。这样就合并了两棵线段树。

注意数组要开够,数的个数只有大于 \(0\) 时才可以作为答案输出。

代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 100005, M = N * 2, INF = 100000;

int rt[N];
int node_cnt;
int h[N], e[M], ne[M], idx;
int n, m;
int fa[N][20], dep[N];
int ans[N];

struct Node {
    int val = 0, id;
    int l = 0, r = 0;
} tr[N * 20 * 4];

void add(int a, int b){
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs_init(int u, int father){
    dep[u] = dep[father] + 1;
    fa[u][0] = father;
    for (int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if (j == father) continue;
        dfs_init(j, u);
    }
}

int lca(int x, int y){
    if (dep[x] < dep[y]) swap(x, y);

    for (int i = 17; i >= 0; i -- ){
        int fax = fa[x][i];
        if (dep[fax] >= dep[y]) x = fax;
    }

    if (x == y) return x;

    for (int i = 17; i >= 0; i -- ){
        int fax = fa[x][i], fay = fa[y][i];
        if (fax != fay) x = fax, y = fay;
    }

    return fa[x][0];
}

void pushup(int u){
    if (tr[tr[u].l].val >= tr[tr[u].r].val) {
        tr[u].val = tr[tr[u].l].val;
        tr[u].id = tr[tr[u].l].id;
    }
    else {
        tr[u].val = tr[tr[u].r].val;
        tr[u].id = tr[tr[u].r].id;
    }
}

void update(int &u, int l, int r, int x, int val){
    if (!u) u = ++ node_cnt;
    if (l == r) {
        tr[u].val += val;
        tr[u].id = x;
        return ;
    }

    int mid = l + r >> 1;
    if (x <= mid) update(tr[u].l, l, mid, x, val);
    else update(tr[u].r, mid + 1, r, x, val);
    pushup(u);
}

int merge(int x, int y, int l, int r){
    if (!x || !y) return x | y;
    if (l == r) {
        tr[x].val += tr[y].val;
        return x;
    }
    int mid = l + r >> 1;
    tr[x].l = merge(tr[x].l, tr[y].l, l, mid);
    tr[x].r = merge(tr[x].r, tr[y].r, mid + 1, r);
    pushup(x);
    return x;
}

void dfs(int u, int father){
    for (int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if (j == father) continue;
        dfs(j, u);
        rt[u] = merge(rt[u], rt[j], 1, INF);
    }
    ans[u] = tr[rt[u]].val > 0 ? tr[rt[u]].id : 0;
}

void debug(int u, int L, int R){
    printf("##%d %d %d %d %d %d %d\n", u, tr[u].l, tr[u].r, L, R, tr[u].val, tr[u].id);
    int mid = L + R >> 1;
    if (tr[u].l) debug(tr[u].l, L, mid);
    if (tr[u].r) debug(tr[u].r, mid + 1, R);
}

int main(){
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    for (int i = 1; i < n; i ++ ){
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);
    }

    dfs_init(1, 0);
    for (int k = 1; k < 18; k ++ )
        for (int u = 1; u <= n; u ++ )
            fa[u][k] = fa[fa[u][k - 1]][k - 1];

    while (m -- ){
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        int l = lca(x, y);
        update(rt[x], 1, INF, z, 1);
        update(rt[y], 1, INF, z, 1);
        update(rt[l], 1, INF, z, -1);
        if (fa[l][0]) update(rt[fa[l][0]], 1, INF, z, -1);
    }

    dfs(1, -1);

    for (int i = 1; i <= n; i ++ ) printf("%d\n", ans[i]);

    // debug(rt[1], 1, INF);
}
posted @ 2024-12-29 11:15  是一只小蒟蒻呀  阅读(7)  评论(0编辑  收藏  举报