主席树

主席树

首先考虑一个比较经典的问题,你有一个静态的数列,每次询问一段区间 (l o r) 内的第 (k) 小。

做法的一句话介绍,巨的人就不要往下翻了。

对于原序列的每个前缀维护一颗线段树,维护这个区间,并且这些线段树满足可减性。

接下具体解释下,

考虑一个静态区间上维护区间信息,一般会想到什么呢?

还记得我们在学线段树的时候曾经是需要维护一个可修改的区间。

我们一共三种做法。

第一种,直接用 (a[]) 记录,每次修改 (O(1)),查询 (O(n))

第二种,通过差分的思想维护前缀和数组 (sum[]) 每次修改 (O(n)) ,查询 (O(1))

第三种,则是线段树,查询,修改都是 (O(log_n)) 的。

那么这个问题只需要维护一段静态区间,那么就用差分的思想。

对于每个前缀都开一颗权值线段树,那么这样的话查询就变得十分的方便。

因为只要判断左边的大小是否大于 (k) 。最裸的线段树问题。

看似很简单,但是空间显然会炸。

你这么做的空间复杂度是 (n * n << 2) 的。

你不炸谁炸?

那么这个时候就可以用到动态开点了,不知道动态开点的可以去看我的 (blog)

这也是主席树的灵魂部分。

记录每个前缀的根,从根开始向前一颗线段树的子树连边。

具体的话是这样实现的。

(a[]) 是原数列,现在处理到 (x) 节点。

你就依次判断 (mid)(a[x]) 之间的关系。

如果 (mid) 大于 (a[x]) 你就把第 (x) 颗线段树当前节点的右儿子连到第 (x - 1) 颗线段树对应的位置。

否则就把第 (x) 颗线段树当前节点的左儿子连到第 (x - 1) 颗线段树对应的位置。

至于另一边就不断递归就行了,最后只需要新建一个节点就行了。

模板

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <queue>
#define N 200020
#define ls x << 1
#define rs x << 1 | 1
#define inf 0x3f3f3f3f
#define inc(i) (++ (i))
#define dec(i) (-- (i))
#define mid ((l + r) >> 1)
#define int long long
#define XRZ 1000000003
#define debug() puts("XRZ TXDY");
#define mem(i, x) memset(i, x, sizeof(i));
#define Next(i, u) for(register int i = head[u]; i ; i = e[i].nxt)
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout);
#define Rep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i <= i##Limit ; inc(i))
#define Dep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i >= i##Limit ; dec(i))
int dx[10] = {1, -1, 0, 0};
int dy[10] = {0, 0, 1, -1};
using namespace std;
inline int read() {
    register int x = 0, f = 1; register char c = getchar();
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
    return x * f;
}
int n, m, T[N << 5], Data[N << 5], Left[N << 5], Right[N << 5], a[N], b[N], rt;
int Build(int l, int r) {
	int now = inc(rt);
	if(l < r) Left[now] = Build(l, mid), Right[now] = Build(mid + 1, r);
	return now;
}
int Updata(int pre, int l, int r, int x) {
	int now = inc(rt);
	Left[now] = Left[pre]; Right[now] = Right[pre]; Data[now] = Data[pre] + 1;
	if(l >= r) return now;
    if(x <= mid) Left[now] = Updata(Left[pre], l, mid, x);
	else Right[now] = Updata(Right[pre], mid + 1, r, x);
	return now;
}
int Query(int x, int y, int l, int r, int k) {
	if(l == r) return l;
	int std = Data[Left[y]] - Data[Left[x]];
	if(std >= k) return Query(Left[x], Left[y], l, mid, k);
	else return Query(Right[x], Right[y], mid + 1, r, k - std);
}
signed main() {
	int n = read(), m = read();
	Rep(i, 1, n) a[i] = b[i] = read();
	sort(b + 1, b + n + 1);
	int M = unique(b + 1, b + n + 1) - b - 1;
	T[0] = Build(1, M);
	Rep(i, 1, n) {
		a[i] = lower_bound(b + 1, b + M + 1, a[i]) - b;
		T[i] = Updata(T[i - 1], 1, M, a[i]);
	}
	Rep(i, 1, m) {
		int x = read(), y = read(), z = read();
		printf("%d
", b[Query(T[x - 1], T[y], 1, M, z)]);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/Flash-plus/p/13834060.html