[算法竞赛进阶指南] 雨天的尾巴 (线段树合并/树链剖分 权值线段树)

题意

给定一棵 n n n 个节点的树和 m m m 次操作,每次操作把 u u u v v v 路径上的节点加上一个颜色,最后询问每个点最多颜色的编号(如果相同取编号最小)

1 ≤ n , m ≤ 1 0 5 , 1 ≤ z ≤ 1 0 5 1 \le n,m \le 10^5,1\le z \le 10^5 1n,m105,1z105

分析:
此题是线段树合并模板题,这里给出树链剖分的做法。
每次操作修改树上的路径,可以用树链剖分维护一下,注意到 z z z 的范围是 1 0 5 10^5 105 ,所以我们不能在树上的每个节点上开一个桶记录颜色,所以可以用权值线段树的动态开点。不过这里有更优做法,因为树链剖分出来的序列对应树上的唯一路径,所以题目的操作就相当于:给定一个序列,每次在 [ l , r ] [l,r] [l,r] 区间添加一个颜色,询问每个点最多颜色的编号。这样就可以用差分的思想,每次在 l l l + 1 +1 +1 r + 1 r + 1 r+1 − 1 -1 1,我们把 l l l 排序,扫描 1 ∼ N 1 \sim N 1N 的每个点,每次遍历这个点的询问,把对这个点的修改在权值线段树上操作,然后查询一下最大的下标。
此题在 acwing \text{acwing} acwing z z z 的数据范围为 1 0 9 10^9 109 所以最好离散化一下。

代码:

#include <bits/stdc++.h>
#define int long long
#define find(x) lower_bound(num.begin(), num.end(), x) - num.begin()
using namespace std;
const int N = 1e5 + 5, M = N << 1;
int z[N], a[N], u[N], v[N], n, m, h[N], e[M], ne[M], idx, id[N], ans[N], mp[N], cnt, dep[N], Size[N], top[N], fa[N], son[N];
vector<int> x[N], num;
struct SegmentTree {
    int l, r, mx, val;
} tr[N << 2];
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs1(int u, int father, int depth) {
    dep[u] = depth, fa[u] = father, Size[u] = 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == father) continue;
        dfs1(j, u, depth + 1);
        Size[u] += Size[j];
        if (Size[son[u]] < Size[j]) son[u] = j;
    }
}
void dfs2(int u,int t) {
    id[u] = ++ cnt, top[u] = t, mp[cnt] = u;
    if (!son[u]) return ;
    dfs2(son[u], t);
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa[u] || j == son[u]) continue;
        dfs2(j, j);
    }
}
void pushup(int u) {
    if (tr[u << 1].mx >= tr[u << 1 | 1].mx) {
        tr[u].mx = tr[u << 1].mx;
        tr[u].val = tr[u << 1].val;
    } else {
        tr[u].mx = tr[u << 1 | 1].mx;
        tr[u].val = tr[u << 1 | 1].val;
    }
}
void build(int u, int l, int r) {
    if (l == r) {
        tr[u] = {l, r, 0, l};
    } else {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}
void modify(int u, int pos, int c) {
    if (tr[u].l == pos && tr[u].r == pos) {
        tr[u].mx += c;
    } else {
        int mid = tr[u].l + tr[u].r >> 1;
        if (pos <= mid) {
            modify(u << 1, pos, c);
        } else {
            modify(u << 1 | 1, pos, c);
        }
        pushup(u);
    }
}
void modify_path(int u, int v, int k) {
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        x[id[top[u]]].push_back(k), x[id[u] + 1].push_back(-k);
        u = fa[top[u]];
    }
    if (dep[u] < dep[v]) swap(u, v);
    x[id[v]].push_back(k), x[id[u] + 1].push_back(-k);
}
signed main() {
    memset(h, -1, sizeof h);
    cin >> n >> m;
    for (int i = 0; i < n - 1; i ++) {
        cin >> u[i] >> v[i];
        add(u[i], v[i]), add(v[i], u[i]);
    }
    dfs1(1, -1, 1), dfs2(1, 1);
    build(1, 1, N - 1);
    for (int i = 1; i <= m; i ++) {
        cin >> u[i] >> v[i] >> z[i];
        num.push_back(z[i]);
    }
    sort(num.begin(), num.end());
    num.erase(unique(num.begin(), num.end()), num.end());
    for (int i = 1; i <= m; i ++) {
        modify_path(u[i], v[i], find(z[i]) + 1);
    }
    for (int i = 1; i < N; i ++) {
        for (int j = 0; j < x[i].size(); j ++) {
            if (x[i][j] > 0) {
                modify(1, x[i][j], 1);
            } else {
                modify(1, -x[i][j], -1);
            }
        }
        ans[mp[i]] = tr[1].mx ? num[tr[1].val - 1] : 0;
    }
    for (int i = 1; i <= n; i ++) cout << ans[i] << endl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值