线段树合并总结

前言

某菜鸡月线段树合并啊?emm……是个好东西!

线段树合并这还用你说?!


定义

顾名思义,就是将两棵线段树所记录的信息整合到一棵线段树上。见下图

图片盗的来源于主席的线段树合并讲义


前置知识

不会的童鞋可以先了解一下(


合并用处

阿巴阿巴……让我思考一哈哈。。

emmm……,线段树合并这个东东吧,通过合并这种鬼畜的操作,节省了时间和空间,阔以让你得到一个 book 思议的复杂度(

反正就很神奇嘛,还挺好用的嘿嘿嘿


合并方式

按常理来说有两种合并方式:

1.在一棵树的基础上合并另一棵树,相当于把另一棵树盖在这棵树上;

2.将两棵树合并的结果存在一棵新的线段树上。

但是如果采用第二种方式,空间肯定会炸的……要对自己有信心

本着节约最光荣的优秀美德,肯定选第一种合并方式啊!

以树 \(x\)\(y\) 为例,我们要将 \(y\) 合并到 \(x\) 上。

因为是动态开点,所以\(x\) ,\(y\) 上的结点信息都可能不全,直接把 \(y\) 上有值的结点赋到 \(x\) 上就好; 对于没有值的结点,直接跳出(因为当前点还未出现,必定不存在有儿子的情况。

合并参考代码:


int merge(int x, int y, int l, int r) {//线段树范围在[l,r]之间
	if(!x || !y) return x + y;//任意一棵树为空即可退出合并
	if(l == r) {
		tr[x].sum += tr[y].sum;//信息合并
		return x;
	}
	int mid = (l + r) >> 1;
	ls(x) = merge(ls(x), ls(y), l, mid);
	rs(x) = merge(rs(x), rs(y), mid + 1, r);
	pushup(x);
	return x;
}

简单吧~


复杂度

阿哲,奇奇怪怪的复杂度。。。我也不会,参考了一下其他博客的分析……以下为 heyuhhh 大佬的分析。

假设我们以 \(n\) 个点为根建立权值线段树,由于我们是动态开点,每颗线段树最后都是一条链,那么空间复杂度和时间复杂度都是 \(O(nlogn)\) 的。最后我们合并的时候,每次 \(merge\) 操作都会减少一个点,所以最后总的合并过程时间复杂度为 \(O(nlogn)\) ,也是十分优秀的了。


经典例题

题单推荐!!!

[Vani有约会]雨天的尾巴 /【模板】线段树合并

题目传送门

\(Solution\):

线段树合并板题啦,同时需要倍增求公共祖先来着。

将每个点看作一棵权值线段树,记录每种救济粮的出现次数,同时每次合并时维护数量最多的救济粮(即维护区间最大值)。查询答案就相当于从叶节点开始向上合并,一直合并到代表所询问区间的结点处。

另外,发放救济粮的修改操作需要倍增求得 \(lca\),这里不多赘述。注意数组不要开小。

\(Code\)

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define ls(x) tr[x].l
#define rs(x) tr[x].r
#define il inline
const int N = 1e5 + 5, M = 8e6 + 5, LOG = 19;

int tot, head[N], nex[N << 1], to[N << 1];
il void add(int x,int y) {
	to[++tot] = y, nex[tot] = head[x], head[x] = tot;
}

int n, m, cnt;
struct seg_Tree {
	int siz, l, r, sum;
}tr[M];

il void pushup(int k) {
	if(!ls(k) || !rs(k)) {
		int tmp = ls(k) + rs(k);
		tr[k].siz = tr[tmp].siz, tr[k].sum = tr[tmp].sum;
	}
	else if(tr[ls(k)].sum >= tr[rs(k)].sum) 
		tr[k].siz = tr[ls(k)].siz, tr[k].sum = tr[ls(k)].sum;
	else 
		tr[k].siz = tr[rs(k)].siz, tr[k].sum = tr[rs(k)].sum;
}

il void change(int &k, int l, int r, int pos, int val) {
	if(!k) k = ++cnt;
	if(l == r) {
		tr[k].sum += val, tr[k].siz = pos;
		return;
	}
	int mid = (l + r) >> 1;
	if(pos <= mid) change(ls(k), l, mid, pos, val);
	else change(rs(k), mid + 1, r, pos, val);
	pushup(k);
}

il int merge(int x, int y, int l, int r) {
	if(!x || !y) return x + y;
	if(l == r) {
		tr[x].sum += tr[y].sum;
		return x;
	}
	int mid = (l + r) >> 1;
	ls(x) = merge(ls(x), ls(y), l, mid);
	rs(x) = merge(rs(x), rs(y), mid + 1, r);
	pushup(x);
	return x;
}

int fa[N][25], dep[N];
il void dfs(int x,int f) {
	int ver;
	fa[x][0] = f, dep[x] = dep[f] + 1;
	for(int i = 1; i < LOG; i ++) fa[x][i] = fa[fa[x][i - 1]][i - 1];
	for(int i = head[x]; i; i = nex[i]) {
		ver = to[i];
		if(ver == f) continue;
		dfs(ver,x);
	}
}

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

int rt[N], ans[N];
il void cale(int x, int f) {
	int ver;
	for(int i = head[x]; i; i = nex[i]) {
		ver = to[i];
		if(ver == f) continue;
		cale(ver,x);
		rt[x] = merge(rt[x], rt[ver], 1, N - 5);
	}
	ans[x] = tr[rt[x]].siz;
	if(!tr[rt[x]].sum) ans[x] = 0;
}

il void read(int &x) {
	x = 0; int f = 1; char s = getchar();
	while(s < '0' || s > '9') {
		if(s == '-') f = -1;
		s = getchar();
	}
	while(s >= '0' && s <= '9') x = x * 10 + s - '0', s = getchar();
	x *= f; 
}
il void write(int x) {
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) write(x / 10), x %= 10;
	putchar(x + '0');
}

int main() {
	int u, v, w, _lca; 
	read(n), read(m);
	for(int i = 1; i < n; i ++) {
		read(u), read(v);
		add(u,v), add(v,u);
	}
	dfs(1,0);
	while(m --) {
		read(u), read(v), read(w);
		_lca = lca(u,v);
		change(rt[u], 1, N - 5, w, 1);
		change(rt[v], 1, N - 5, w, 1);
		change(rt[_lca], 1, N - 5, w, -1);
		change(rt[fa[_lca][0]], 1, N - 5, w, -1);
	}
	cale(1,0);
	for(int i = 1; i <= n; i ++) write(ans[i]), puts("");
	return 0;
}

----------------------------------------------可可爱爱的分割线-------------------------------------------------

Monkey King

题目传送门

\(Solution\):

呐,根据题意就可以知道,本题仍然是将每个结点看作一棵权值线段树,记录以该结点为根结点的子树的所有点权值。相同的,每次合并的时候维护一个最大值就好。

但是与上题不同的是,本题多出一个修改操作,即 每个猴子的强壮值将在一场决斗后减少为原先的一半。但是思考一下,发现其实并不难,可以将该操作看成删去树中原来最大强壮值所在的结点,在插入一个点权为强壮值一半的新结点。问题就迎刃而解啦~

至于删除和插入操作,都挺简单的,看代码应该就能懂……

\(Code\):


#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e5 + 5, M = 4e6 + 5, ed = 32768;

int n, m, cnt;
int rt[N], ls[M], rs[M], tag[M];

void insert(int &k, int l, int r, int pos) {
	if(!k) k = ++cnt;
	tag[k] ++;
	if(l == r) return;
	int mid = (l + r) >> 1;
	if(pos <= mid) insert(ls[k], l, mid, pos);
	else insert(rs[k], mid + 1, r, pos);
}

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

void del(int &k, int l, int r, int pos) {
	if(!k) return;
	tag[k] --;
	if(l == r) return;
	int mid = (l + r) >> 1;
	if(pos <= mid) del(ls[k], l, mid, pos);
	else del(rs[k], mid + 1, r, pos); 
}

int query(int k, int l, int r) {
	if(!k) return 0;
	if(l == r) return l;
	int mid = (l + r) >> 1;
	if(tag[rs[k]]) return query(rs[k], mid + 1, r);
	return query(ls[k], l, mid);
}

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

void read(int &x) {
	x = 0; int f = 1; char s = getchar();
	while(s < '0' || s > '9') {
		if(s == '-') f = -1;
		s = getchar();
	}
	while(s >= '0' && s <= '9') x = x * 10 + s - '0', s = getchar();
	x *= f; 
}
void write(int x) {
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) write(x / 10), x %= 10;
	putchar(x + '0');
}

int main() {
	int x, u, v, fu, fv;
	while(~scanf("%d",&n)) {
		for(int i = 0; i <= cnt; i ++) tag[i] = ls[i] = rs[i] = 0;
		memset(rt,0,sizeof(rt)), cnt = 0;
		for(int i = 1; i <= n; i ++) read(x), fa[i] = i, insert(rt[i], 1, ed, x);
		read(m);
		while(m --) {
			read(u), read(v);
			fu = find(u), fv = find(v);
			if(fu == fv) {
				puts("-1");
				continue; 
			}
			u = query(rt[fu], 1, ed), v = query(rt[fv], 1, ed);
			del(rt[fu], 1, ed, u), del(rt[fv], 1, ed, v);
			insert(rt[fu], 1, ed, u >> 1), insert(rt[fv], 1, ed, v >> 1);
			fa[fu] = fv, rt[fv] = merge(rt[fu], rt[fv], 1, ed);
			write(query(rt[fv], 1, ed)), puts(""); 
		}
	}
	return 0;
}

----------------------------------------------可可爱爱的分割线-------------------------------------------------

Lomsat gelral

题目传送门

\(Solution\):

害,都是套路。

跟 雨天的尾巴 那道题差不多的思路,只不过题目求的是 出现次数最多的颜色编号和,在维护上有一点点不同:

对于结点 \(k\)\(x\)\(y\) 为左右儿子。\(sum_x\) 存的是 \(x\) 上的答案(出现次数最多的颜色编号和), \(siz_x\)\(x\) 上颜色出现的最大次数。

比较 \(siz_x\)\(siz_y\)

  • 如果一边更大,那新的 \(sum\)\(siz\) 都为那一边的 \(sum\)\(siz\)
  • 否则两边相等,\(siz\) 不变, \(sum\)\(sum_x\) \(+\) \(sum_y\)(因为为颜色编号之和)。

void pushup(int k) {
	if(tr[ls(k)].siz > tr[rs(k)].siz) 
		tr[k].sum = tr[ls(k)].sum, tr[k].siz = tr[ls(k)].siz;
	else if(tr[ls(k)].siz < tr[rs(k)].siz) 
		tr[k].sum = tr[rs(k)].sum, tr[k].siz = tr[rs(k)].siz;
	else 
		tr[k].sum = tr[ls(k)].sum + tr[rs(k)].sum, tr[k].siz = tr[rs(k)].siz;
}

其他操作都是模板了。。不讲。

\(Code\):

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define ls(x) tr[x].l
#define rs(x) tr[x].r
#define il inline
const int N = 1e5 + 5, M = 5e6 + 5;

int tot, head[N], nex[N << 1], to[N << 1], a[N];
il void add(int x,int y) {
	to[++tot] = y, nex[tot] = head[x], head[x] = tot;
}

int n, m, cnt;
struct seg_Tree {
	int sum, l, r, siz;
}tr[M];

il void pushup(int k) {
	if(tr[ls(k)].siz > tr[rs(k)].siz) 
		tr[k].sum = tr[ls(k)].sum, tr[k].siz = tr[ls(k)].siz;
	else if(tr[ls(k)].siz < tr[rs(k)].siz) 
		tr[k].sum = tr[rs(k)].sum, tr[k].siz = tr[rs(k)].siz;
	else 
		tr[k].sum = tr[ls(k)].sum + tr[rs(k)].sum, tr[k].siz = tr[rs(k)].siz;
}

il void change(int &k, int l, int r, int pos, int val) {
	if(!k) k = ++cnt;
	if(l == r) {
		tr[k].siz += val, tr[k].sum = l;
		return;
	}
	int mid = (l + r) >> 1;
	if(pos <= mid) change(ls(k), l, mid, pos, val);
	else change(rs(k), mid + 1, r, pos, val);
	pushup(k);
}

il int merge(int x, int y, int l, int r) {
	if(!x || !y) return x + y;
	if(l == r) {
		tr[x].siz += tr[y].siz, tr[x].sum = l;
		return x;
	}
	int mid = (l + r) >> 1;
	ls(x) = merge(ls(x), ls(y), l, mid);
	rs(x) = merge(rs(x), rs(y), mid + 1, r);
	pushup(x);
	return x;
}

int rt[N], ans[N];
il void cale(int x, int f) {
	int ver;
	for(int i = head[x]; i; i = nex[i]) {
		ver = to[i];
		if(ver == f) continue;
		cale(ver,x);
		merge(rt[x], rt[ver], 1, N - 5);
	}
	change(rt[x], 1, N - 5, a[x], 1);
	ans[x] = tr[rt[x]].sum;
}

il void read(int &x) {
	x = 0; int f = 1; char s = getchar();
	while(s < '0' || s > '9') {
		if(s == '-') f = -1;
		s = getchar();
	}
	while(s >= '0' && s <= '9') x = x * 10 + s - '0', s = getchar();
	x *= f; 
}
il void write(int x) {
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) write(x / 10), x %= 10;
	putchar(x + '0');
}

int main() {
	int u, v; 
	read(n);
	for(int i = 1; i <= n; i ++) read(a[i]), rt[i] = i, cnt ++;
	for(int i = 1; i < n; i ++) {
		read(u), read(v);
		add(u,v), add(v,u);
	}
	cale(1,0);
	for(int i = 1; i <= n; i ++) write(ans[i]), putchar(' ');
	return 0;
}

----------------------------------------------可可爱爱的分割线-------------------------------------------------

Blood Cousins

题目传送门

\(Solution\):

朕乏了,明日再议……

归期不定

\(Code\):


#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5 + 5, M = 7e6 + 5, LOG = 18;

int tot, num, vis[N], head[N], nex[N << 1], to[N << 1];
void add(int x,int y) {
	to[++tot] = y, nex[tot] = head[x], head[x] = tot;
}

int n, m, cnt, tr[M], ls[M], rs[M];

void pushup(int k) {
	tr[k] = tr[ls[k]] + tr[rs[k]];
}

void change(int &k, int l, int r, int pos, int val) {
	if(!k) k = ++cnt;
	if(l == r) {
		tr[k] += val;
		return;
	}
	int mid = (l + r) >> 1;
	if(pos <= mid) change(ls[k], l, mid, pos, val);
	else change(rs[k], mid + 1, r, pos, val);
	pushup(k);
}

int merge(int x, int y) {
	if(!x || !y) return x + y;
	int cur = ++cnt;
	tr[cur] = tr[x] + tr[y];
	ls[cur] = merge(ls[x], ls[y]);
	rs[cur] = merge(rs[x], rs[y]);
	return cur;
}

int query(int k, int l, int r, int pos) {
	if(!k) return 0;
	if(l == r) return tr[k];
	int mid = (l + r) >> 1;
	if(pos <= mid) return query(ls[k], l, mid, pos);
	return query(rs[k], mid + 1, r, pos);
}

int fa[N][25], dep[N], rt[N];
void dfs(int x,int f) {
	int ver;
	fa[x][0] = f, dep[x] = dep[f] + 1;
	for(int i = 1; i < LOG; i ++) fa[x][i] = fa[fa[x][i - 1]][i - 1];
	for(int i = head[x]; i; i = nex[i]) {
		ver = to[i];
		if(ver == f) continue;
		dfs(ver,x);
		rt[x] = merge(rt[x], rt[ver]);
	}
	change(rt[x], 1, n, dep[x], 1);
}

int find(int x, int y) {
	for(int i = LOG - 1; i >= 0; i --) {
		if(y & (1 << i)) x = fa[x][i];
	}
	return x;
}

void read(int &x) {
	x = 0; int f = 1; char s = getchar();
	while(s < '0' || s > '9') {
		if(s == '-') f = -1;
		s = getchar();
	}
	while(s >= '0' && s <= '9') x = x * 10 + s - '0', s = getchar();
	x *= f; 
}
void write(int x) {
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) write(x / 10), x %= 10;
	putchar(x + '0');
}

int main() {
	int u, v, pos; 
	read(n);
	for(int i = 1; i <= n; i ++) {
		read(u);
		if(!u) vis[++num] = i;
		else add(u,i), add(i,u);
	}
	for(int i = 1; i <= num; i ++) dfs(vis[i],0);
	read(m);
	while(m --) {
		read(u), read(v);
		pos = find(u,v);
		if(!pos) putchar('0');
		else write(query(rt[pos],1,n,dep[u]) - 1);
		putchar(' ');
	}
	return 0;
}

----------------------------------------------可可爱爱的分割线-------------------------------------------------

Blood Cousins Return

题目传送门

\(Solution\):

\(Code\):


#include <cstdio>
#include <algorithm>
#include <cstring>
#include <set>
#include<iostream>
#include <map>
#include<vector>
using namespace std;
const int N = 1e5 + 5, M = 2e6 + 5, LOG = 18;

set<int> st[M];
int n, cnt, num, t, tr[M], ls[M], rs[M];
int tot, head[N], nex[N << 1], to[N << 1];
void add(int x,int y) {
	to[++tot] = y, nex[tot] = head[x], head[x] = tot;
}

void change(int &k, int l, int r, int pos, int val) {
	if(!k) k = ++num;
	if(l == r) {
		if(!tr[k]) tr[k] = ++cnt;
		st[tr[k]].insert(val);
		return;
	}
	int mid = (l + r) >> 1;
	if(pos <= mid) change(ls[k], l, mid, pos, val);
	else change(rs[k], mid + 1, r, pos, val);
}

void merge(int &x, int y, int l, int r) {
	if(!x || !y) {x += y; return;}
	if(l == r) {
		if(!tr[x]) tr[x] = tr[y];
		else {
			if(st[tr[x]].size() < st[tr[y]].size()) swap(tr[x], tr[y]);
			for(set<int> :: iterator it = st[tr[y]].begin(); it != st[tr[y]].end(); it ++) 
				st[tr[x]].insert(*it);
			st[tr[y]].clear();
		}
		return;
	}
	int mid = (l + r) >> 1;
	merge(ls[x], ls[y], l, mid), merge(rs[x], rs[y], mid + 1, r);
}

int query(int k, int l, int r, int pos) {
	if(l == r) return st[tr[k]].size();
	int mid = (l + r) >> 1;
	if(pos <= mid) return query(ls[k], l, mid, pos);
	return query(rs[k], mid + 1, r, pos);
}

int rt[N], dep[N], val[N], ans[N];
void dfs(int x,int f) {
	dep[x] = f;
	for(int i = head[x]; i; i = nex[i]) dfs(to[i],f + 1);
}

struct question {
	int k,id;
	question() {}
	question(int K, int ID) {
		k = K, id = ID;
	}
}; 
vector<question> Q[N];
void solve(int x,int f) {
	int ver;
	change(rt[x], 1, n, dep[x], val[x]);
	for(int i = head[x]; i; i = nex[i]) {
		ver = to[i];
		if(ver == f) continue;
		solve(ver,x);
		merge(rt[x], rt[ver], 1, n);
	}
	for(int i = 0; i < Q[x].size(); i ++) ans[Q[x][i].id] = query(rt[x], 1, n, Q[x][i].k); 
}

void read(int &x) {
	x = 0; int f = 1; char s = getchar();
	while(s < '0' || s > '9') {
		if(s == '-') f = -1;
		s = getchar();
	}
	while(s >= '0' && s <= '9') x = x * 10 + s - '0', s = getchar();
	x *= f; 
}
void write(int x) {
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) write(x / 10), x %= 10;
	putchar(x + '0');
}
map<string,int> mp;
string s;
int main() {
	int u, v, q; 
	read(n);
	for(int i = 1; i <= n; i ++) {
		cin >> s;
		if(!mp[s]) val[i] = mp[s] = ++t;
		else val[i] = mp[s];
		read(u), add(u,i);
	}
	read(q), dfs(0,0);
	for(int i = 1; i <= q; i ++) {
		read(u), read(v);
		if(dep[u] + v <= n) Q[u].push_back((question){dep[u] + v,i});
	}
	solve(0,0);
	for(int i = 1; i <= q; i ++) write(ans[i]), puts("");
	return 0;
}


总结

总结?没有总结欸……

就,就这个东西还挺好用的哈,但是要注意空间会不会开小或者爆啥的……别问我是怎么知道的。


参考文章

顺序随机哈……

--------------------------------------\(It\) \(will\) \(be\) \(finished\) \(soon\).------------------------------------

posted @ 2021-07-20 21:16  Spring-Araki  阅读(74)  评论(0编辑  收藏  举报