普及贴,大牛绕道。。。。
主席树简直神,看了很多关于这个算法的资料,还是不能一下子明白,建议学习主席树的时候,算法讲解的话,直接眼睛扫一遍就好了,不要指望别人能讲清楚,还是要自己看代码理解。
老规矩,从这个题 POJ 2104 说起,题意求任意区间的第
K
大。不带修改的静态的主席树。
以数据:
5
5 2 1 4 3
2 5 3
为例
首先我们呢需要把所有的数离散化一下。。
从代码看起:
建树:
int build(int L,int R)
{
int rt = tot++;
c[rt] = 0;
if(L != R) {
int mid = (L + R) >> 1;
lson[rt] = build(L, mid);
rson[rt] = build(mid + 1, R);
}
return rt;
}
这段代码呢,就是建一棵树,同时记录下每个节点的编号以及其左右儿子的编号。如下:
2.原数组中从后往前依次建树
//线段树上从 rt 节点到 pos 在节点 路径上 生成新的节点
//同时维护 原线段树 与 新生成节点 同位置上的 c数组值
//即c[newRoot] = c[root] + val;
//同时T[i] 记录新路径 的起始节点
int update(int rt,int pos,int val)
{
int nrt = tot++, tmp = nrt;
c[nrt] = c[rt] + val;
int L = 1, R = tol;
while(L < R){
int mid = (L + R) >> 1;
if(pos <= mid) {
lson[nrt] = tot++, rson[nrt] = rson[rt];
nrt = lson[nrt], rt = lson[rt];
R = mid;
}
else {
rson[nrt] = tot++, lson[nrt] = lson[rt];
nrt = rson[nrt], rt = rson[rt];
L = mid + 1;
}
c[nrt] = c[rt] + val;
}
return tmp;
}
//tol是离散化后数组的长度,t数组存的是原数组排好序的数组。
T[n + 1] = build(1, tol);
for(int i = n;i;i --) {
p = lower_bound(t + 1, t + 1 + tol, a[i]) - t;
T[i] = update(T[i+1], p, 1);
}
这段代码的解释上面写的很清楚了,以update最后一个数3为例,就会新建成下面的线段树,注意到只有从根节点到3的路径上的点被赋予的新的值,而且同时更新了
3.查找区间第K大
个人觉得这里是最难理解的。
int query(int rt_L,int rt_R,int k)
{
int l = 1, r = tol;
//每次查询区间 [L,R] 内的数出现在[1, mid] 内的次数
//随之进行二分
while(l < r) {
int mid = (l + r) >> 1;
if(c[lson[rt_L]] - c[lson[rt_R]] >= k){
rt_L = lson[rt_L];
rt_R = lson[rt_R];
r = mid;
}
else {
k -= c[lson[rt_L]] - c[lson[rt_R]];
rt_L = rson[rt_L];
rt_R = rson[rt_R];
l = mid + 1;
}
}
return l;
}
//找到对应的区间,查询第K大
//从前面可以知道,T数组存的是原数组每个数对应的线段树的顶点编号。
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",t[query(T[l], T[r+1], k)]);
从这里我们就能弄明白前面的c数组到底有什么用,还要要弄清楚一个问题,什么条件下要取 r= mid, 什么条件下要取 l = mid +1,为什么要这么取?能够把这个问题搞明白,就可以去膜拜主席了,Orz。
我们想想
c
数组到底存的是什么,从前面画的图就可以知道当update时经过线段树上某个节点就会有一次c[nrt] = c[rt] + val,而且只有当update的这个数(例如前面的3)在节点对应的区间的时候才会经过这个点,所以每颗线段树上每个节点
所以很自然的,查询第K大的时候,把区间拆半,
[L,mid]
,
[mid+1,R]
,如果区间内的数出现在
[L,mid]
内的次数大于等于
K
的话,说明第
*以上讲的是不带修改的静态主席树,如果带修改的话,还要弄个树状数组来优化。总的来讲呢,主席树就是很神啦,自己好好体验吧!
4.全部代码
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <map>
#include <vector>
#include <set>
#include <string>
#define PB push_back
#define FT first
#define SD second
#define MP make_pair
#define INF 0x3f3f3f3f
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> P;
const int N = 10 + 1e5,M = 30 * N;
int n, q, tol, tot;
int a[N], t[N];
int c[M], T[M], lson[M], rson[M];
void init()
{
tot = 0;
for(int i = 1;i <= n;i ++) t[i] = a[i];
sort(t+1, t+1+n);
tol = unique(t+1, t+1+n) - t - 1;
}
int build(int L,int R)
{
int rt = tot++;
c[rt] = 0;
if(L != R) {
int mid = (L + R) >> 1;
lson[rt] = build(L, mid);
rson[rt] = build(mid + 1, R);
}
return rt;
}
//线段树上从 rt 节点到 pos 在节点 路径上 生成新的节点
//同时维护 原线段树 与 新生成节点 同位置上的 c数组值
//即c[newRoot] = c[root] + val;
//同时T[i] 记录新路径 的起始节点
int update(int rt,int pos,int val)
{
int nrt = tot++, tmp = nrt;
c[nrt] = c[rt] + val;
int L = 1, R = tol;
while(L < R){
int mid = (L + R) >> 1;
if(pos <= mid) {
lson[nrt] = tot++, rson[nrt] = rson[rt];
nrt = lson[nrt], rt = lson[rt];
R = mid;
}
else {
rson[nrt] = tot++, lson[nrt] = lson[rt];
nrt = rson[nrt], rt = rson[rt];
L = mid + 1;
}
c[nrt] = c[rt] + val;
}
return tmp;
}
int query(int rt_L,int rt_R,int k)
{
int l = 1, r = tol;
//每次查询区间 [L,R] 内的数出现在[1, mid] 内的次数
//随之进行二分
while(l < r) {
int mid = (l + r) >> 1;
// cout << rt_L << " " << rt_R << " | " << c[lson[rt_L]] << " " << c[lson[rt_R]] << endl;
if(c[lson[rt_L]] - c[lson[rt_R]] >= k){
rt_L = lson[rt_L];
rt_R = lson[rt_R];
r = mid;
}
else {
k -= c[lson[rt_L]] - c[lson[rt_R]];
rt_L = rson[rt_L];
rt_R = rson[rt_R];
l = mid + 1;
}
}
return l;
}
int main()
{
freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int p, l, r, k;
while(scanf("%d%d",&n, &q) == 2){
for(int i = 1;i <= n;i ++) {
scanf("%d",&a[i]);
}
init();
T[n + 1] = build(1, tol);
for(int i = n;i;i --) {
p = lower_bound(t + 1, t + 1 + tol, a[i]) - t;
T[i] = update(T[i+1], p, 1);
}
while(q --) {
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",t[query(T[l], T[r+1], k)]);
}
}
//system("pause");
return 0;
}