可持续化线段树,也叫主席树,很久以前【也没多久】写了一篇求区间第k大的,那时候理解不深,现在再写一个加深理解
可持续化,具体来说就是可以访问历史版本,以实现跨时间的操作。
怎么实现呢?
试想我们修改线段树的时候,修改前是旧版本,修改后是新版本,想想新版本和旧版本有什么区别?
区别就在于从根节点到被修改点的路径上的点改变了,其他的点是不变的
那么这个时候我们新建一个根节点,对于不变的儿子指向旧版本的儿子,对于有变化的儿子就新建一个节点
这样一来我们有每一个时刻的根节点,就可以访问到每个时刻的线段树
空间会不会暴呢?
每条路径只有logn个点,m次修改我们只多了mlogn个点,可以承受
但实在空间卡得很紧时就要用指针了
比如洛谷P3931
下面给出指针版题解代码【并非完全是普遍的写法,由题简化】:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long int
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define fo(i,x,y) for (int i = (x); i <= (y); i++)
#define Redge(u) for (int k = head[u]; k != -1; k = edge[k].next)
using namespace std;
const int maxn = 1000005,maxm = 10000005,INF = 1000000000;
inline int read(){
int out = 0,flag = 1;char c = getchar();
while (c < 48 || c > 57) {if (c == '-') flag = -1; c = getchar();}
while (c >= 48 && c <= 57) {out = out * 10 + c - 48; c = getchar();}
return out * flag;
}
int N,M,A[maxn];
struct node{
int v;
node *ls,*rs;
node() {ls = rs = NULL;}
};
node* rt[maxn];
int rti = 0,pos;
void Build(node **u,int l,int r){
*u = new node;
if (l == r){
(*u)->v = A[l];
}else {
int mid = l + r >> 1;
Build(&(*u)->ls,l,mid);
Build(&(*u)->rs,mid + 1,r);
}
}
void change(node *pre,node **u,int l,int r,int v){
(*u) = new node;
*(*u) = *pre;
if (l == r){
(*u)->v = v;
}else {
int mid = l + r >> 1;
if (mid >= pos) change(pre->ls,&(*u)->ls,l,mid,v);
else change(pre->rs,&(*u)->rs,mid + 1,r,v);
}
}
int Query(node *u,int l,int r){
if (l == r) return u->v;
else {
int mid = l + r >> 1;
if (mid >= pos) return Query(u->ls,l,mid);
else return Query(u->rs,mid + 1,r);
}
}
int main()
{
N = read();
M = read();
REP(i,N) A[i] = read();
Build(&rt[0],1,N);
int pre,cmd,v;
while (M--){
pre = read();
cmd = read();
pos = read();
if (cmd & 1){
v = read();
change(rt[pre],&rt[++rti],1,N,v);
}else {
printf("%d
",Query(rt[++rti] = rt[pre],1,N));
}
}
return 0;
}