学了可持久化线段树有一段时间了,一直没拿出时间来整理一下,刚好今天有空,就写一写。
可持久化的含义是对于每次修改操作都将产生一个新版本的线段树,并且旧版本的线段树仍然保留可以随时访问。
基于这个目的,我们可以采用类似动态开点的方法构建一颗线段树。
假设我们已经有了一个版本的线段树,我们要对这个线段树进行操作,我们首先复制前一颗线段树的根节点,然后在执行修改操作的时候,把所有途经的节点全部新建,其他节点保留。
一、包含单点修改,区间查询的可持久化线段树的实现
struct persist_segtree{
struct Node{ //线段树的节点
int lft,rgt; //左儿子指针和右儿子指针
int num; //当前节点保存的值
}segs[maxn];
int rt[maxn]; //历史版本的线段树根节点指针
int tot; //节点数目
void init(){
tot = 0;
memset(segs,0,sizeof(segs));
}
inline int getnum(int rt){
return rt?segs[rt].num:0;
}
void add(int &rt,int lft,int rgt,int pos,int num){ //修改操作
int nrt = ++tot; //沿途的节点都要新建
segs[nrt] = segs[rt]; //新建的节点是原节点的拷贝,然后再修改
rt = nrt;
int mid = (lft + rgt)/2;
if(lft == rgt){ //如果到达底部
segs[rt].num += num;
return ;
}
if(pos <= mid) //如果要修改的位置在左侧
add(segs[rt].lft,lft,mid,pos,num);
else //如果要修改的位置在右侧
add(segs[rt].rgt,mid+1,rgt,pos,num);
segs[rt].num = getnum(segs[rt].lft) + getnum(segs[rt].rgt); //更新本节点的值
}
int query(int rt,int lft,int rgt,int beg,int end){
if(!rt) return 0;
if(beg <= lft && rgt <= end) return segs[rt].num;
if(beg > rgt || end < lft) return 0;
int mid = (lft + rgt)/2;
return query(segs[rt].lft,lft,mid,beg,end) + query(segs[rt].rgt,mid+1,rgt,beg,end);
}
}
1、查询,比如要查询第5个版本线段树的[3,199919991999]的区间和,那么查询语句可以这样写:
segtree.query(segtree.rt[5],1,1000000000,3,199919991999);
2、修改,比如在201720172017位置插入数值12345:
segtree.rt[t] = segtree.rt[t-1];
segtree.add(segtree.rt[t],1,1000000000,201720172017,12345);
二、区间修改
我也不知道为什么要把区间修改列在这里,其实区间修改就是lazy标记,和普通的线段树没有什么区别,有一点需要注意,就是在 标记下传 的时候,也要新建节点。
/*标记下传*/
/*注意在标记下传的时候也要新开节点*/
void down(int rt,int lft,int rgt){
if(!segs[rt].lazy) return ;
int mid = (lft + rgt)/2;
segs[++tot] = segs[segs[rt].lft];
segs[tot].num += segs[rt].lazy*(mid-lft+1);
segs[tot].lazy += segs[rt].lazy;
segs[rt].lft = tot;
segs[++tot] = segs[segs[rt].rgt];
segs[tot].num += segs[rt].lazy*(rgt-mid);
segs[tot].lazy += segs[rt].lazy;
segs[rt].rgt = tot;
segs[rt].lazy = 0;
}
查询和修改操作也要根据lazy标记做适当的修改
void add(int &rt,int lft,int rgt,int beg,int end,int num){
int nrt = ++tot;
segs[nrt] = segs[rt];
rt = nrt;
int mid = (lft + rgt)/2;
if(beg <= lft && rgt <= end){
segs[rt].num += (rgt-lft+1)*num;
segs[rt].lazy += num;
return ;
}
if(rgt < beg || end < lft) return ;
down(rt,lft,rgt);
if(lft <= end) add(segs[rt].lft,lft,mid,beg,end,num);
if(rgt >= beg) add(segs[rt].rgt,mid+1,rgt,beg,end,num);
segs[rt].num = getnum(segs[rt].lft) + getnum(segs[rt].rgt);
}
int query(int rt,int lft,int rgt,int beg,int end){
if(!rt) return 0;
if(beg <= lft && rgt <= end) return segs[rt].num;
if(beg > rgt || end < lft) return 0;
int mid = (lft + rgt)/2;
down(rt,lft,rgt);
return query(segs[rt].lft,lft,mid,beg,end) + query(segs[rt].rgt,mid+1,rgt,beg,end);
}