= Vain学习(C/C++ acm算法 python Linux Golang)的个人网站 =

浅谈主席树

Posted on

主席树

我学习主席树,其实已经好几个月了,但是一直没有理解透彻,只能看着板子敲,
今天有回顾了一下主席树,然后就突然有了新的理解。

主席树学习的前置技能

  • 权值线段树
  • 动态开点

权值线段树

学习权值线段树后,会了解求整体区间第k小,这个操作和主席树模板题中
的求区间第k小操作是一样的

动态开点

首先主席树其实是一颗残树,但一般的线段树其实是预处理整个区间,
所以一般建线段树的方法,并不适合建主席树,相反动态开点建树的方法,
是可以在树的任何一个分支去建一条链的,原因在于,动态开点其实是给你一棵
虚拟的树,我们按照我们要插入树中值的大小,在树中对应的位置,然后递归寻找这个位置,
在找的过程中,把经过的点开出来,而且动态开点无需考虑数的大小,只需考虑数量

主席树

主席树其实的思想其实就是权值线段树,权值线段树可以求整体区间的第k小,原因在于,
我们已知整体区间中不同区间的数的个数,呢么主席树因为有动态开点和权值线段树加持,
我们可以,用动态开点建立n个版本的残缺线段树,如果我们要询问区间的第k小,我们就可以在
l-r版本的残缺线段树中进行,权值线段树的操作,这点和前缀和思路一样


模板题

参考代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <cstdio>
#include <algorithm>
#include <vector>
#include <iostream>
#include <map>
#include <queue>
#include <cstring>
#include <string>
#include <cstdlib>
#include <cmath>
#include <set>
using namespace std;
const int inf = (1 << 30);
typedef long long ll;
const int maxn = 2e5 + 5;
int rt[maxn], cnt;
struct node
{
int ls, rs, sum;
} tr[maxn * 40];
void inser(int ver, int &now, int L, int R, int pos)
{
now = ++cnt;
tr[now] = tr[ver];
tr[now].sum++;
if (L == R)
return;
int mid = L + R >> 1;
if (mid >= pos)
inser(tr[ver].ls, tr[now].ls, L, mid, pos);
else
inser(tr[ver].rs, tr[now].rs, mid + 1, R, pos);
}
int ask(int ver, int now, int L, int R, int pos)
{
if (L == R)
return L;
int mid = L + R >> 1;
int sum = tr[tr[now].ls].sum - tr[tr[ver].ls].sum;
if (sum >= pos)
return ask(tr[ver].ls, tr[now].ls, L, mid, pos);
else
return ask(tr[ver].rs, tr[now].rs, mid + 1, R, pos - sum);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n, x, q;
cin >> n >> q;
for (int i = 1; i <= n; i++)
cin >> x, inser(rt[i - 1], rt[i], -inf, inf, x);
while (q--)
{
int ql, qr, ik;
cin >> ql >> qr >> ik;
cout << ask(rt[ql - 1], rt[qr], -inf, inf, ik) << endl;
}
}