线段树合并

\(\text{luogu-4556}\)

村落里一共有 \(n\) 座房屋,并形成一个树状结构。然后救济粮分 \(m\) 次发放,每次选择两个房屋 \((x, y)\),然后对于 \(x\)\(y\) 的路径上(含 \(x\)\(y\))每座房子里发放一袋 \(z\) 类型的救济粮。

然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。

\(1 \leq n, m \leq 10^5\)\(1 \leq a,b,x,y \leq n\)\(1 \leq z \leq 10^5\)


线段树合并模板题。

以下部分题解来自于 线段树合并 (学习笔记)(26.1.19) - Yuriha - 博客园

概述

可以将两棵线段树合并为一颗的算法,一般用在动态开点线段树和权值线段树中。

对于一些需要维护子树权值的题目中,父亲节点需要去合并自己的两个子树节点,对于普通的线段树,我们只需要去权值合并就行,但是对于动态开点线段树,如果我们没有一些优化,就会导致时间空间发生一些问题了,所以相应的线段树合并就诞生了。

思路

线段树合并面临的共有三种情况。

  1. 儿子都为空,我们直接不管这个节点即可。
  2. 儿子都不为空,我们就需要递归去加两个儿子。
  3. 有一个儿子为空,我们可以直接将这个儿子的信息加到当前线段树,另一个不管。

实现

这道题还用到了动态开点和权值线段树和树上差分。

对于动态开点,就是去正常的进行线段树操作,在进入的时候判断一下某个下标是否存在,如果不存在,就去新建一个下标,所以要专门储存左儿子和右儿子,不能直接去 p<<1 这样类似的操作。

对于权值线段树,这是一个把权值作为下标进行操作的数据结构,它维护了在值域 \([1, V]\) 区间内的所有权值计数,在第二次 \(dfs\) 操作,对于某个节点,遍历其子树将所有节点加至它自己,\(t_x\) 做的是以 \(x\) 为节点的权值线段树的根。

对于树上差分,主要就是可以用链表方式存储其对应的所有修改操作,其他无太多。

#include<iostream>
#include<cstdio>
#include<vector> 
#include<cmath>
using namespace std;
#define MAXN 100005

long long read() {
	long long x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

long long n, m, lg[MAXN], fa[MAXN][30], dep[MAXN], dn, hd[MAXN], ans[MAXN];
long long ls[MAXN * 80], rs[MAXN * 80], t[MAXN], mx[MAXN * 80], cnt;
struct node { long long w, nxt; } res[MAXN << 2];
vector<long long> v[MAXN];

void dfs(long long x, long long f) {
	fa[x][0] = f, dep[x] = dep[f] + 1;
	for(int i = 1; i <= lg[dep[x]]; i ++)
		fa[x][i] = fa[fa[x][i - 1]][i - 1];
	for(auto y : v[x]) if(y != f) dfs(y, x);
	return;
}

long long lca(long long x, long long y) {
	if(dep[x] < dep[y]) swap(x, y);
	while(dep[x] > dep[y]) 
		x = fa[x][lg[dep[x] - dep[y]] - 1];
	if(x == y) return x;
	for(int i = lg[dep[x]] - 1; i >= 0; i --) 
		if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
	return fa[x][0];
}

void add(long long x, long long w) {
	res[++ dn] = {w, hd[x]}, hd[x] = dn;
	return;
}

long long merge(long long x, long long y, long long l, long long r) {
	if(!x || !y) return x | y;
	if(l == r) { mx[x] += mx[y]; return x; }
	long long mid = (l + r) >> 1;
	ls[x] = merge(ls[x], ls[y], l, mid);
	rs[x] = merge(rs[x], rs[y], mid + 1, r);
	mx[x] = max(mx[ls[x]], mx[rs[x]]);
	return x;
}

void update(long long &x, long long l, long long r, long long p, long long w) {
	if(!x) x = (++ cnt);
	if(l == r) { mx[x] += w; return; }
	long long mid = (l + r) >> 1;
	if(p <= mid) update(ls[x], l, mid, p, w);
	else update(rs[x], mid + 1, r, p, w);
	mx[x] = max(mx[ls[x]], mx[rs[x]]);
	return;
}

long long query(long long x, long long l, long long r) {
	if(l == r) return l;
	long long mid = (l + r) >> 1;
	if(mx[x] == mx[ls[x]]) return query(ls[x], l, mid);
	return query(rs[x], mid + 1, r);
}

void dfs1(long long x, long long f) {
	for(auto y : v[x]) if(y != f) 
		dfs1(y, x), t[x] = merge(t[x], t[y], 1, 1e5);
	for(int i = hd[x]; i; i = res[i].nxt) 
		update(t[x], 1, 1e5, abs(res[i].w), (res[i].w >= 0) ? 1 : -1);
	if(mx[t[x]]) ans[x] = query(t[x], 1, 1e5);
	return;
}

int main() {
	n = read(), m = read();
	for(int i = 1; i < n; i ++) {
		long long x = read(), y = read();
		v[x].push_back(y), v[y].push_back(x);
	}
	for(int i = 1; i < MAXN; i ++) lg[i] = lg[i >> 1] + 1;
	dfs(1, 0);
	for(int i = 1; i <= m; i ++) {
		long long x = read(), y = read(), z = read();
		long long t = lca(x, y);
		add(x, z), add(y, z), add(t, -z), add(fa[t][0], -z);
	}
	dfs1(1, 0);
	for(int i = 1; i <= n; i ++) cout << ans[i] << "\n";
	return 0;
}

\(\text{luogu-3224}\)

永无乡包含 \(n\) 座岛,编号从 \(1 \sim n\) ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 \(n\) 座岛排名,名次用 \(1 \sim n\) 来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛到达另一个岛。

现在有两种操作:

B x y 表示在岛 \(x\) 与岛 \(y\) 之间修建一座新桥。

Q x k 表示询问当前与岛 \(x\) 连通的所有岛中第 \(k\) 重要的是哪座岛,即所有与岛 \(x\) 连通的岛中重要度排名第 \(k\) 小的岛是哪座,请你输出那个岛的编号。

\(1 \leq m \leq n \leq 10^5\), \(1 \leq q \leq 3 \times 10^5\)


考虑用动态开点权值线段树,维护重要度。并查集维护连通性。

每次建边时把两个线段树合并就好了。

询问操作直接线段树上二分即可。

#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 200005

long long read() {
	long long x = 0, f = 1;
	char c = getchar();
	while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
	while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
	return x * f;
}

struct node { long long l, r, ls, rs, w; } t[MAXN << 5];
long long n, m, q, fa[MAXN], T[MAXN], ans[MAXN], cnt;

long long find(long long x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }

long long insert(long long x, long long l, long long r) {
	long long p = (++ cnt);
	t[p].l = l, t[p].r = r, t[p].w = 1;
	if(l == r) return p;
	long long mid = (l + r) >> 1;
	if(x <= mid) t[p].ls = insert(x, l, mid);
	else t[p].rs = insert(x, mid + 1, r);
	return p;
}

long long merge(long long x, long long y, long long l, long long r) {
	if(!x || !y) return x | y;
	long long p = (++ cnt), mid = (l + r) >> 1;
	t[p].l = l, t[p].r = r, t[p].w = t[x].w + t[y].w;
	t[p].ls = merge(t[x].ls, t[y].ls, l, mid);
	t[p].rs = merge(t[x].rs, t[y].rs, mid + 1, r);
	return p;
}

long long query(long long x, long long k) {
	if(t[x].w < k) return -1;
	if(t[x].l == t[x].r) return ans[t[x].l];
	if(t[t[x].ls].w >= k) return query(t[x].ls, k);
	return query(t[x].rs, k - t[t[x].ls].w);
}

int main() {
	n = read(), m = read();
	for(int i = 1; i <= n; i ++) {
		long long x = read(); 
		T[i] = insert(x, 1, n), ans[x] = fa[i] = i;
	} 
	for(int i = 1; i <= m; i ++) {
		long long x = find(read()), y = find(read());
		if(x != y) fa[x] = y, T[y] = merge(T[x], T[y], 1, n);
	}
	q = read();
	while(q --) {
		char c; cin >> c; 
		long long x = read(), y = read();
		if(c == 'B') {
			x = find(x), y = find(y);
			if(x != y) fa[x] = y, T[y] = merge(T[x], T[y], 1, n);
		}
		else cout << query(T[find(x)], y) << "\n";
	}
	return 0;
}
posted @ 2026-01-20 21:14  So_noSlack  阅读(0)  评论(0)    收藏  举报