学了可持久化线段树有一段时间了,一直没拿出时间来整理一下,刚好今天有空,就写一写。
可持久化的含义是对于每次修改操作都将产生一个新版本的线段树,并且旧版本的线段树仍然保留可以随时访问。
基于这个目的,我们可以采用类似动态开点的方法构建一颗线段树。
假设我们已经有了一个版本的线段树,我们要对这个线段树进行操作,我们首先复制前一颗线段树的根节点,然后在执行修改操作的时候,把所有途经的节点全部新建,其他节点保留。
一、包含单点修改,区间查询的可持久化线段树的实现
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);}