【学习笔记】 线段树进阶

线段树合并

实现

每次递归到叶子节点合并。

线段树合并 单次时间复杂度 \(O(n \log n)\)
证明:每次递归会让区间长度减半,所以最多会递归 \(O(\log n)\) 层,而区间长度为 \(n\),所以叶子节点有\(n\) 个,所以最多合并 \(O(n\log n)\) 次。
因为如果对每个节点都开一颗线段树,空间会爆炸,所以使用动态开点线段树,空间复杂度 \(O(m\log n)\)
证明:十分显然,要进行 \(m\) 次操作,每次最多增加 \(O(\log n)\) 个节点。

例题 雨天的尾巴

code

#include<bits/stdc++.h>
#define N 4000010
#define C 100000
#define M N << 2 
#define fo(a, b, c) for(int b = a; b <= c; b++)
#define _fo(a, b, c) for(int b = a; b >= c; b--)
#define Fo(a, b) for(auto a : b)
#define pb push_back
#define int long long
using namespace std;
int n, m, f[N][30], d[N];
int rt[N], lc[M], rc[M], t[M], id[M], tn = 0, ans[N];
vector<int>e[N];
void pushup(int p){
	if(t[lc[p]] > t[rc[p]]) id[p] = id[lc[p]];
	if(t[lc[p]] < t[rc[p]]) id[p] = id[rc[p]];
	if(t[lc[p]] == t[rc[p]]) id[p] = min(id[rc[p]], id[lc[p]]);
	t[p] = max(t[lc[p]], t[rc[p]]);
}
void upd(int &p, int l, int r, int x, int k){
	if(!p) p = ++tn;
	if(l == r){
		return id[p] = x, t[p] += k, void();
	}
	int md = (l + r) >> 1;
	if(x <= md) upd(lc[p], l, md, x, k);
	else upd(rc[p], md + 1, r, x, k);
	pushup(p);
}
void dfs(int u, int fa){
	f[u][0] = fa, d[u] = d[fa] + 1;
	fo(1, i, 20) f[u][i] = f[f[u][i - 1]][i - 1];
	Fo(v, e[u]) if(v ^ fa) dfs(v, u);
}
int Lca(int x, int y){
	if(d[x] < d[y]) swap(x, y);
	int len = d[x] - d[y];
	fo(0, i, 20){
		if(len & 1) x = f[x][i];
		len >>= 1;
	}
	if(x == y) return x;
	_fo(20, i, 0){
		if(f[x][i] ^ f[y][i]) x = f[x][i], y = f[y][i];
	}
	return f[x][0];
}
int mg(int x, int y, int l, int r){
	if(!x || !y) return x | y;
	if(l == r) return t[x] += t[y], x;
	int md = (l + r) >> 1;
	lc[x] = mg(lc[x], lc[y], l, md), rc[x] = mg(rc[x], rc[y], md + 1, r);
	pushup(x);
	return x;
}
void dfs1(int u, int fa){
	Fo(v, e[u]){
		if(v ^ fa){
			dfs1(v, u);
			rt[u] = mg(rt[u], rt[v], 1, C);
		}
	}
	ans[u] = id[rt[u]];
	if(!t[rt[u]]) ans[u] = 0;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin >> n >> m;
	fo(1, i, n - 1){
		int x, y; cin >> x >> y;
		e[x].pb(y), e[y].pb(x);
	}
	dfs(1, 0);
	fo(1, i, m){
		int x, y, z; cin >> x >> y >> z;
		upd(rt[x], 1, C, z, 1), upd(rt[y], 1, C, z, 1);
		int l = Lca(x, y);
		//cout << x << ' ' << y << ' ' << l << endl;
		upd(rt[l], 1, C, z, -1), upd(rt[f[l][0]], 1, C, z, -1);
	}
	dfs1(1, 0);
	fo(1, i, n) cout << ans[i] << "\n";
	return 0;
}

主席树

简介

主席树是一种可以用来在 任意时刻 查询维护历史版本的树形结构。

实现

详见代码

用途一 可持久化数组

比较板子,不说了

用途二 求区间第 k 大

\([l,r]\) 的第 \(k\) 大,可以转化为求一个最小的数 \(x\),所有小于等于它的数在 \([l,r]\) 中出现的次数大于等于 \(k\)。一个数在 \([l,r]\) 中出现的次数等价于它在 \([1,r]\) 中出现的次数减去他在 \([1,l]\) 中出现的次数。

posted @ 2025-05-31 19:51  GuoSN0410  阅读(46)  评论(0)    收藏  举报