树上线段树合并(模板题)

Acwing

洛谷

树上线段树合并(模板题)_第1张图片
题意:

  • 给定一颗树,m 次操作每次让树中一条路径上每个结点 z 物品携带个数 + 1
  • 求每个结点最终携带最多物品的种类是哪个,有多个就输出编号最小的

思路:

  • 有两种写法:树链剖分 + 权值线段树、树上线段树合并。

  • 此题是一个线段树合并的板子题,做下笔记:

  • 树上线段树合并,本质就是递归过程中合并两两节点线段树信息,彷佛很暴力但魔幻的能优化为 O ( n l o g n ) O(nlogn) O(nlogn) 的复杂度,类似启发式合并。

  • 具体写法:对树中任意点 动态 地建一颗线段树。因为只需要最终输出,此算法为离线算法,对树上一条路径存放救济粮的过程完全可以用 树上差分 优化。

合并模板:

//离线
int merge(int u, int v, int l, int r) {
    if (!u)return v;//任意点不存在就返回兄弟结点,这就是能保证O(nlogn)的关键
    if (!v)return u;
    if (l == r) {
        tr[u].mx += tr[v].mx;
        tr[u].pos = tr[u].mx ? l : 0;
		//中间这部分依附过程会破坏旧信息,只能离线算法中用,优点是不多占用空间
        return u;
    }
    int mid = l + r >> 1;
    tr[u].l = merge(tr[u].l, tr[v].l, l, mid);
    tr[u].r = merge(tr[u].r, tr[v].r, mid + 1, r);
    //指针修改后再pushup维护信息
    pushup(u);
    return u;
}

//在线
int merge(int u, int v, int l, int r) {
    if (!u)return v;//任意点不存在就返回兄弟结点
    if (!v)return u;
    int p = ++rdx;
    if (l == r) {
        tr[p].mx = tr[u].mx + tr[v].mx;
        tr[p].pos = tr[p].mx ? l : 0;
        return p;
    }
    int mid = l + r >> 1;
    tr[p].l = merge(tr[u].l, tr[v].l, l, mid);
    tr[p].r = merge(tr[u].r, tr[v].r, mid + 1, r);
    pushup(p);
    return p;
}

C o d e : Code: Code:

#include
#include
#include
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define debug(x) cout<<"target is "<<x<<endl
#define forr(a,b,c) for(int a=b;a<=c;a++)
#define all(a) a.begin(),a.end()
#define oper(a) operator<(const a& ee)const
#define endl "\n"
#define ul (u << 1)
#define ur (u << 1 | 1)
//#pragma GCC optimize(3, "Ofast", "inline")
using namespace std;

typedef long long ll;
typedef pair<int, int> PII;

const int N = 1e5 + 10, M = 200010, MM = 110;
int INF = 0x3f3f3f3f, mod = 1e9 + 7;
ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, siz;
int h[N], ne[M], e[M], idx;
int fa[N][17], dep[N], ans[N];

void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs_pre(int x, int fat) {
    for (int i = h[x]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fat)continue;
        dep[j] = dep[x] + 1;
        fa[j][0] = x;
        for (int k = 1; k <= 16; k++)
            fa[j][k] = fa[fa[j][k - 1]][k - 1];
        dfs_pre(j, x);
    }
}
int lca(int a, int b) {
    if (dep[a] < dep[b])swap(a, b);
    for (int i = 16; i >= 0; i--)
        if (dep[fa[a][i]] >= dep[b])
            a = fa[a][i];
    if (a == b)return a;
    for (int i = 16; i >= 0; i--)
        if (fa[a][i] != fa[b][i]) {
            a = fa[a][i];
            b = fa[b][i];
        }
    return fa[a][0];
}

//线段树合并能做到玄学的 O(nlogn)
struct tree
{
    int l, r;
    int mx, pos;
}tr[N * 60];//空间理论要开 N * 4 * 4 * logn,实际没那么多
int root[N], rdx;

void pushup(tree& root, tree& le, tree& re) {
    root.mx = max(le.mx, re.mx);
    root.pos = le.mx >= re.mx ? le.pos : re.pos;
}
void pushup(int u) {
    pushup(tr[u], tr[tr[u].l], tr[tr[u].r]);
}
void modify(int u, int l, int r, int x, int d) {
    if (l == r) {
        tr[u].mx += d;
        tr[u].pos = tr[u].mx ? l : 0;
        return;
    }
    int mid = l + r >> 1;
    if (x <= mid) {
        if (!tr[u].l)tr[u].l = ++rdx;
        modify(tr[u].l, l, mid, x, d);
    }
    else {
        if (!tr[u].r)tr[u].r = ++rdx;
        modify(tr[u].r, mid + 1, r, x, d);
    }
    pushup(u);
}
//线段树离线合并函数,如果是在线就新建点 p,不要影响原有数据
int merge(int u, int v, int l, int r) {
    if (!u)return v;//任意点不存在就返回兄弟结点
    if (!v)return u;
    if (l == r) {
        tr[u].mx += tr[v].mx;
        tr[u].pos = tr[u].mx ? l : 0;//差分维护后处理位置信息
        return u;
    }
    int mid = l + r >> 1;
    tr[u].l = merge(tr[u].l, tr[v].l, l, mid);
    tr[u].r = merge(tr[u].r, tr[v].r, mid + 1, r);
    pushup(u);
    return u;
}
void dfs(int x, int fat) {
    for (int i = h[x]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fat)continue;
        dfs(j, x);
        root[x] = merge(root[x], root[j], 1, siz);
        //和子节点的线段树一一合并
    }
    ans[x] = tr[root[x]].pos;
    //合并后记入答案
}

void solve() {
    cin >> n >> m;
    mem(h, -1);
    siz = 1e5;//线段树每个位置维护的是某个物品 z 
    //区间维护最大个数和位置

    for (int i = 2; i <= n; i++) {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a);
    }
    dep[0] = 0, dep[1] = 1;
    dfs_pre(1, -1);

    for (int i = 1; i <= n; i++)root[i] = ++rdx;
    //每个点建树根

    for (int i = 1; i <= m; i++) {
        int a, b, x;
        cin >> a >> b >> x;
        int p = lca(a, b);
        //差分思想,不过是将差分的过程用线段树合并优化了

        modify(root[a], 1, siz, x, 1);
        modify(root[b], 1, siz, x, 1);
        modify(root[p], 1, siz, x, -1);
        if (fa[p][0])modify(root[fa[p][0]], 1, siz, x, -1);
    }

    dfs(1, -1);

    forr(i, 1, n)cout << ans[i] << endl;
}

signed main() {
    cinios;
    int T = 1;
    forr(t, 1, T) {
        solve();
    }
    return 0;
}
/*
*/

你可能感兴趣的:(高级数据结构,#,LCA,知识点笔记,线段树,数据结构,算法,树,树上差分)