线段树合并

相似内容 启发式合并

线段树合并是和并两个动态开点的线段树。

正常的线段树是在 build 的中把左右儿子建出来。动态开点线段树是用的时候再建,而且不需要 build 函数。线段树合并可以合并权值线段树,最值线段树,和差线段树等,以和差线段树为例

  • 对于两棵线段树都有的节点,新的线段树上该节点值为两者和。
  • 对于一棵线段树有的节点,新的线段树保存该节点的值。
  • 再对左右子树递归处理。
void merge(int &x, int y, int l, int r) {
	if (!x || !y) {
		x += y; return ;
	}
	if (l == r)
		//对叶子进行合并 
	int mid = (l + r) >> 1;
	merge(ls[x], ls[y], l, mid); merge(rs[x], rs[y], mid + 1, r);
	pushup(x); //上传信息
} 

线段树合并的时间复杂度和启发式合并一样,都是 O ( n log ⁡ n ) O(n \log n) O(nlogn)

【模板】线段树合并

给定一棵 n n n 个点构成的树,有 m m m 个操作,每个操作将 ( x , y ) (x,y) (x,y) 路径上的每个点放一个数 z z z。求操作结束后,每个点最多的数字是多少。

对于每个点建立一棵动态开点的权值线段树,支持查询区间最值和最值在线段树上的代表的数,把路径加操作用差分转化为单点加。再用线段树合并一下即可,就是细节有点多。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5, INF = 0x3f3f3f3f;
inline int read() {
	int x = 0, f = 0; char ch = 0;
	while (!isdigit(ch)) f |= ch == '-', ch = getchar();
	while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
	return f ? -x : x;
}
struct edge{
    int to, nxt;
}e[N];
int head[N], tot, cnt;
void addedge(int x, int y){
    e[++tot].to = y, e[tot].nxt = head[x], head[x] = tot;
}
int sz[N], fa[N], son[N], dep[N], top[N];
void dfs1(int x, int f) {
    fa[x] = f, dep[x] = dep[f] + 1, sz[x] = 1;
    for (int i = head[x]; i; i = e[i].nxt){
        int y = e[i].to;
        if (y != f){
            dfs1(y, x); sz[x] += sz[y];
            if (sz[y] > sz[son[x]]) son[x] = y;
        }
    }
}
void dfs2(int x, int rt) {
    top[x] = rt;
    if (son[x]) dfs2(son[x], rt);
    for (int i = head[x]; i; i = e[i].nxt){
        int y = e[i].to;
        if (y != son[x] && y != fa[x]) dfs2(y, y);
    }
}
int lca(int x, int y) {

    for (; top[x] != top[y]; x = fa[top[x]]) if (dep[top[x]] < dep[top[y]]) swap(x, y);
    return dep[x] < dep[y] ? x : y;
}
int ls[N << 5], rs[N << 5], val[N << 5], num[N << 5]; //开 n log n 的空间 
void pushup(int x) {
	if (val[ls[x]] >= val[rs[x]]) val[x] = val[ls[x]], num[x] = num[ls[x]]; //编号最小,所以 >=  
	else val[x] = val[rs[x]], num[x] = num[rs[x]];
}
void update(int &x, int l, int r, int pos, int k) {
	if (!x) x = ++cnt;
	if (l == r) {
		val[x] += k;
		num[x] = l;
		return ;
	}
	int mid = (l + r) >> 1;
	if (pos <= mid) update(ls[x], l, mid, pos, k);
	else update(rs[x], mid + 1, r, pos, k);
	pushup(x); 
}
void merge(int &x, int y, int l, int r) {
	if (!x || !y) {
		x += y; return ;
	}
	if (l == r) {
		val[x] += val[y];
		return ;
	}
	int mid = (l + r) >> 1;
	merge(ls[x], ls[y], l, mid); merge(rs[x], rs[y], mid + 1, r);
	pushup(x);
}
int ans[N], root[N];
void dfs(int x) {
	for (int i = head[x]; i; i = e[i].nxt) {
		int y = e[i].to;
		if (y == fa[x]) continue;
		dfs(y);
		merge(root[x], root[y], 1, 1e5); 
	} 
	if (val[root[x]]) ans[x] = num[root[x]]; //没有救济粮为 0 
}
int main() {
	int n = read(), m = read();
	for (int i = 1; i < n; i++) {
		int x = read(), y = read();
		addedge(x, y), addedge(y, x);
	} 
	dfs1(1, 0); dfs2(1, 1); 
	while (m--) {
		int x = read(), y = read(), z = read();
		int t = lca(x, y);
		update(root[x], 1, 1e5, z, 1);
		update(root[y], 1, 1e5, z, 1);
		update(root[t], 1, 1e5, z, -1);
		if (fa[t]) update(root[fa[t]], 1, 1e5, z, -1);
	}
	//puts("awa");
	dfs(1);
	for (int i = 1; i <= n; i++) printf("%d\n", ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值