定义
可持久化线段树。据说是某大佬学不会划分树,所以自己研究出了这一更为高效的算法(不愧是大佬,%%%)。由于主席树博大精深,这里分成几种类型的主席树分别讨论(无修改,维护修改,这篇是静态不修改主席树)。
作用
查询区间内某个数出现次数、查找区间第 k k k大值。
原理
学主席树,首先要学会权值线段树。权值线段树的概念比较基础。我给你一个数组
a
a
a,你统计出这个数组里有那些数,排个序,再另开一个数组
b
b
b储存这些数各自的出现次数,然后对数组
b
b
b建立线段树维护和。这样子一来就得到了范围内里的数出现的总次数。我们要找第
k
k
k大的数,只需要从根节点开始,将
k
k
k与左子节点的值进行比较,如果
k
k
k大于左子节点的值,说明第
k
k
k大的值在左子树囊括的区间内,反之在右子树囊括的区间内,且为第(
k
k
k-左子树的值)大。
知道了这个后,就是主席树了,主席树要维护的是不确定区间的第
k
k
k大,有了上面的思路我们自然可以想到,能不能每次都建立一颗权值线段树,然后查询呢?这其实就是主席树的基本思想。只不过,直接这么来肯定TLE,这就需要优化,那么怎么优化呢?我们注意到,对于两个维护
(
1
,
a
)
和
(
1
,
b
)
(
a
<
b
)
(1,a)和(1,b)(a < b)
(1,a)和(1,b)(a<b)的区间,如果这两棵线段树形态与根节点完全相同,那我们把每个节点的对应值相减就能得到维护这一段区间的线段树。那么我们是不是可以对每个节点都建一颗
(
1
,
n
)
(1,n)
(1,n)的线段树呢?可是这样会MLE,那么怎么办呢?
我们注意到,每次后一个节点的权值线段树相对于前一个节点,只增加了一个叶子节点的值,相当于只是修改了一个从根到叶子的路,那么我们能不能保证只修改这条路,剩下的全都利用上一个根节点的值?用结构体来简单描述一下这个思想
struct node{
int val;
node * lchild;
node * rchild;
} rt[maxn];
int num[maxn];
void init()
{
//假定rt[0]已经建好(全部为0,有m个叶子结点的树)
for(int i = 1; i <= n; i++){
int l = 1, r = m;
rt[i].val = rt[i-1].val +1;
node pos = rt[i], pre = rt[i-1];
while(l != r){
int mid = (l+r) >> 1;
node tmp;
if(a[i] <= mid){
r = mid;
pos.rchild = pre.rchild;
tmp.val = pre.lchild.val + 1;
pos.lchild = tmp;
pos = pos.lchild, pre = pre.lchild;
}
else{
l = mid + 1;
pos.lchild = pre.lchild;
tmp.val = pre.rchild.val + 1;
pos.rchild = tmp;
pos = pos.rchild, pre = pre.rchild;
}
}
}
}
这就是修改一条链的方法,最后的查询操作见模板。
总的来说,静态主席树大致分为四个部分:离散化、建树、赋值、查询。
以下是模板:
int c[maxn*30], ls[maxn*30], rs[maxn*30], T[maxn];
int a[maxn], b[maxn];
int m, n, q, cnt;
void init()
{
cnt = -1;
for(int i = 1; i <= n; i++)
b[i] = a[i];
sort(b+1, b+1+n);
m = unique(b+1, b+1+n) - b -1;
}
int build(int l, int r)
{
c[++cnt] = 0;
int rt = cnt;
if(l != r){
int mid = (l+r) >> 1;
ls[rt] = build(l, mid);
rs[rt] = build(mid+1, r);
}
return rt;
}
int update(int rt, int pos, int val)
{
c[++cnt] = c[rt] + val;
int res, nrt;
res = nrt = cnt;
int l = 1, r = m;
while(l != r){
int mid = (l + r) >> 1;
if(pos <= mid){
ls[nrt] = ++cnt, rs[nrt] = rs[rt];
nrt = ls[nrt], rt = ls[rt];
r = mid;
}
else{
ls[nrt] = ls[rt], rs[nrt] = ++cnt;
nrt = rs[nrt], rt = rs[rt];
l = mid+1;
}
c[nrt] = c[rt] + val;
}
return res;
}
int query(int lrt, int rrt, int k)
{
int l = 1, r = m;
while(l != r){
int mid = (l+r) >> 1;
if(c[ls[lrt]] - c[ls[rrt]] >= k){
r = mid;
lrt = ls[lrt], rrt = ls[rrt];
}
else{
k -= c[ls[lrt]] - c[ls[rrt]];
l = mid+1;
lrt = rs[lrt], rrt = rs[rrt];
}
}
return b[l];
}
例题
题目分析
这题其实是一条非常裸的静态主席树题,只需要套板子就行(主要是因为实在找不到什么其他的例题。。。)
代码:
#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
const int maxn = 1e5 + 10;
int c[maxn*30], ls[maxn*30], rs[maxn*30], T[maxn];
int a[maxn], b[maxn];
int m, n, q, cnt;
void init()
{
cnt = -1;
for(int i = 1; i <= n; i++)
b[i] = a[i];
sort(b+1, b+1+n);
m = unique(b+1, b+1+n) - b -1;
}
int build(int l, int r)
{
c[++cnt] = 0;
int rt = cnt;
if(l != r){
int mid = (l+r) >> 1;
ls[rt] = build(l, mid);
rs[rt] = build(mid+1, r);
}
return rt;
}
int update(int rt, int pos, int val)
{
c[++cnt] = c[rt] + val;
int res, nrt;
res = nrt = cnt;
int l = 1, r = m;
while(l != r){
int mid = (l + r) >> 1;
if(pos <= mid){
ls[nrt] = ++cnt, rs[nrt] = rs[rt];
nrt = ls[nrt], rt = ls[rt];
r = mid;
}
else{
ls[nrt] = ls[rt], rs[nrt] = ++cnt;
nrt = rs[nrt], rt = rs[rt];
l = mid+1;
}
c[nrt] = c[rt] + val;
}
return res;
}
int query(int lrt, int rrt, int k)
{
int l = 1, r = m;
while(l != r){
int mid = (l+r) >> 1;
if(c[ls[lrt]] - c[ls[rrt]] >= k){
r = mid;
lrt = ls[lrt], rrt = ls[rrt];
}
else{
k -= c[ls[lrt]] - c[ls[rrt]];
l = mid+1;
lrt = rs[lrt], rrt = rs[rrt];
}
}
return b[l];
}
int main()
{
scanf("%d%d", &n, &q);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
init();
T[n+1] = build(1, m);
for(int i = n; i; i--){
int pos = lower_bound(b+1, b+1+m, a[i]) - b;
T[i] = update(T[i+1], pos, 1);
}
for(int i = 1; i <= q; i++){
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
printf("%d\n", query(T[l], T[r+1], k));
}
}