可持久化线段树题目链接
主席树因为创建者名字缩写为
H
J
T
的大佬创建的所以又叫做主席树
\sout{ 主席树因为创建者名字缩写为\mathbf{HJT}的大佬创建的所以又叫做主席树}
主席树因为创建者名字缩写为HJT的大佬创建的所以又叫做主席树
前置芝士
首先要明白主席树做的什么:对一棵线段树的节点进行修改,然后查询第 k k k次操作后的线段树中的某个节点
显然,如果我们将每次修改操作后的线段树存下来会消耗大量空间,但是,如果我们不存储修改操作后的整棵树,而是只增加新修改的点
举个栗子
\mathbf{举个栗子}
举个栗子
对于每一次修改,我们只需要建立修改点和与其相关点的新节点就可以了,其他的点直接连在之前版本的树上就可以了
图看懂了就很简单了
\tiny{\sout{\mathbf{图看懂了就很简单了}}}
图看懂了就很简单了
由此观察主席树的存储和线段树有明显的差异
struct Node{
int l,r,val; //l存储左节点坐标,r存储右节点坐标,val存储当前节点数值
}
建树
int built_tree(int l,int k,int r){
k = ++ top;
if(l == r){
t[k] = a[l];
return k;
}
int mid = l + ((r - l) >> 1);
t[k].l = built_tree(l,t[k].l,mid);
t[k].r = built_tree(mid + 1,t[k].r,r);
return k;
}
将版本 r t rt rt中的第 x x x个点值改为 v a l val val
int update(int l,int k,int r,int x,int val){
k = clone(k);
if(l == r){
t[l].val = val;
return k;
}
int mid = l + ((r - l) >> 1);
if(x <= mid) t[k].l = update(l,t[k].l,mid,x,val);
else t[k].r = update(mid + 1,t[k].r,r,x,val);
return k;
}
c l o n e clone clone是在每次修改前先将上一个版本的节点复制一次
int clone(int k);
t[++ top] = t[k];
return top;
查询
int quary(int l,int k,int r,int x){
if(l == r) return t[k].val;
int mid = l + ((r - l) >> 1);
if(x <= mid) return quary(l,t[k].l,mid,x);
else return quary(mid + 1,t[k].r,r,x);
}
下面附上ac代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 1e6 + 5;
int a[MAXN];
int top = 0;
int root[MAXN];
struct Node{
int l,r,val;
}t[MAXN * 25];
int read(){
int n = 0,l = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') l = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
n = (n << 1) + (n << 3) + (c & 15);
c = getchar();
}
return n * l;
}
int built_tree(int l,int k,int r){
k = ++ top;
if(l == r){
t[k].val = a[l];
return top;
}
int mid = l + ((r - l) >> 1);
t[k].l = built_tree(l,k,mid);
t[k].r = built_tree(mid + 1,k,r);
return k;
}
int clone(int k){
t[++ top] = t[k];
return top;
}
int update(int l,int k,int r,int x,int val){
k = clone(k);
if(l == r){
t[k].val = val;
return k;
}
int mid = l + ((r - l) >> 1);
if(x <= mid) t[k].l = update(l,t[k].l,mid,x,val);
else t[k].r = update(mid + 1,t[k].r,r,x,val);
return k;
}
int quary(int l,int k,int r,int x){
if(l == r) return t[k].val;
int mid = l + ((r - l) >> 1);
if(x <= mid) return quary(l,t[k].l,mid,x);
else return quary(mid + 1,t[k].r,r,x);
}
int main(){
int n = read(),m = read();
for(int i = 1; i <= n; i ++)
a[i] = read();
root[0] = built_tree(1,0,n);
for(int i = 1; i <= m; i ++){
int rt = read(),f = read(),x = read();
if(f == 1){
int y = read();
root[i] = update(1,root[rt],n,x,y);
}
if(f == 2){
printf("%d\n",quary(1,root[rt],n,x));
root[i] = root[rt];
}
}
return 0;
}
利用主席树维护区间里的第k大
以一道题目为例主席树
看到题目可以想到对于每个
[
l
,
r
]
[l,r]
[l,r]进行一次
s
o
r
t
sort
sort,但显然不行
考虑数据结构,一维数组,区间询问,就想到了线段树
题目要求第k小,所以线段树维护的是
[
l
,
r
]
内不同数出现的次数
\small\mathbf{[l,r]内不同数出现的次数}
[l,r]内不同数出现的次数
对于
[
1
,
i
]
树与
[
1
,
i
+
1
]
树
[1,i]树与[1,i + 1]树
[1,i]树与[1,i+1]树后者只是多了
a
[
i
+
1
]
插入带来的影响
\small\mathbf{a[i +1]插入带来的影响}
a[i+1]插入带来的影响
这和上面是一样的,不需要新建整棵树,只需要新建和新节点有关的就可以了
考虑怎么维护
\small\mathbf{考虑怎么维护}
考虑怎么维护
首先对原序列进行排序
然后再去重
举个栗子
\mathbf{举个栗子}
举个栗子
5 4 3 3 2 1
排序后为
1 2 3 3 4 5
去重
1 2 3 4 5
inline int update(int l,int k,int r,int x){
k = clone(k);
if(l == r) return k;
int mid = l + ((r- l) >> 1);
if(x <= mid) t[k].l = update(l,t[k].l,mid,x);
else t[k].r = update(mid + 1,t[k].r,r,x);
return k;
}
inline int query(int u,int v,int l,int t,int k){
if(l >= r) return l;
int x = t[t[v].l].sum - t[t[u].l].sum;
int mid = l + ((r - l) >> 1);
if(x >= k) return query(t[u].l,t[v].l,l,mid,k);
else return query(t[u].r,t[v].r,mid + 1,r,k - x);
}
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 200005;
int root[MAXN];
int a[MAXN];
int b[MAXN];
int top = 0;
struct Node{
int l,r,sum;
}t[MAXN * 20];
inline int read(){
int n =0,l = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') l = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
n = (n << 1) + (n << 3) + (c & 15);
c = getchar();
}
return n * l;
}
inline int built_tree(int l,int k,int r){
k = ++ top;
t[k].sum = 0;
if(l == r) return k;
int mid = l + ((r - l) >> 1);
t[k].l = built_tree(l,t[k].l,mid);
t[k].r = built_tree(mid + 1,t[k].r,r);
return k;
}
inline int clone(int k){
t[++ top] = t[k];
t[top].sum ++;
return top;
}
inline int update(int l,int k,int r,int x){
k = clone(k);
if(l == r) return k;
int mid = l + ((r - l) >> 1);
if(x <= mid) t[k].l = update(l,t[k].l,mid,x);
else t[k].r = update(mid + 1,t[k].r,r,x);
return k;
}
inline int query(int u,int v,int l,int r,int k){
if(l >= r) return l;
int x = t[t[v].l].sum - t[t[u].l].sum;
int mid = l + ((r - l) >> 1);
if(x >= k) return query(t[u].l,t[v].l,l,mid,k);
else return query(t[u].r,t[v].r,mid + 1,r,k - x);
}
int main(){
int n = read(),q = read();
for(int i = 1; i <= n; i ++)
b[i] = a[i] = read();
sort(b + 1,b + n + 1);
int m = unique(b + 1,b + n + 1) - b - 1;
root[0] = built_tree(1,0,m);
for(int i = 1; i <= n; i ++){
int k = lower_bound(b + 1,b + m + 1,a[i]) - b;
root[i] = update(1,root[i - 1],m,k);
}
for(int i = 1; i <= q; i ++){
int x = read(),y = read(),z = read();
printf("%d\n",b[query(root[x - 1],root[y],1,m,z)]);
}
return 0;
}