前言
这和可持久化Trie树怎么这么像呢?
“疯狂动物城”的码量太大了吧(oj过了但洛谷才20pts 强烈建议加强数据 )
空间计算量可真“小”
打得不好,让各位巨佬见笑了
既造福后人,也是给自己提供一些启发和…
是什么
可持久化线段树是一种支持历史版本查询的数据结构,它可以在每一次修改后生成新的版本,同时保留旧版本的信息。在许多需要追踪历史版本的问题中,可持久化线段树都可以提供高效的解决方案。
可持久化线段树和普通线段树的本质区别在于,它每次修改时都会生成一个新的版本。因此,我们需要额外的空间来存储每一个版本中线段树的节点信息。当需要查询一个历史版本时,我们可以通过遍历该版本的线段树节点来得到相应的结果。
可持久化线段树的时间复杂度与普通线段树相同,均为 O ( log n ) O(\log n) O(logn)。在实际应用中,我们可以通过对可持久化线段树的节点进行优化,来进一步提高查询和修改的效率。
实现
(无说明则拿可持久化权值线段树为例)
关于定义
struct Node{
int lson,rson,cnt;
//......其他属性
}
自认为打得通俗易懂
单点修改
void add(int left,int right,int val,int lst,int &now){
a[(now=++sum)]=a[lst];//
a[now].cnt++;
if(left==right)return;
int mid=(left+right)/2;
if(val<=mid)add(left,mid,val,a[lst].lson,a[now].lson);
else add(mid+1,right,val,a[lst].rson,a[now].rson);
}
其中now
为当前版本的点的编号,lst
为上一版本的点的编号
区间修改
以“疯狂动物城”为例
void change(int left,int right,int L,int R,long long num,int lst,int &now){
if(left>R||right<L)return;
a[now=++sum]=a[lst];
if(left>=L&&right<=R){
a[now].tag+=num;
a[now].num.add(num);
return;
}if(left==right)return;
int mid=(left+right)>>1;
if(L<=mid)change(left,mid,L,R,num,a[lst].lson,a[now].lson);
if(mid+1<=R)change(mid+1,right,L,R,num,a[lst].rson,a[now].rson);
a[now].num=a[a[now].lson].num+a[a[now].rson].num;//左右子树合并
a[now].num.add(a[now].tag);
}
其中tag
为懒标记
单点查询
int find(int aa,int bb,int left,int right,int val){
if(left==right)return a[aa].cnt-a[bb].cnt;
int mid=(left+right)/2;
if(val<=mid)return find(a[aa].lson,a[bb].lson,left,mid,val)+a[a[aa].rson].cnt-a[a[bb].rson].cnt;
return find(a[aa].rson,a[bb].rson,mid+1,right,val);
}
区间查询
以“疯狂动物城”为例
void ask_ans(int left,int right,int L,int R,int x,long long ss){
if(left>right||!x)return;
if(left>=L&&right<=R){
tt=a[x].num;
tt.add(ss);
ans=ans+tt;
return;
}
int mid=(left+right)>>1;
if(L<=mid)ask_ans(left,mid,L,R,a[x].lson,(ss+a[x].tag)%mod);
if(mid+1<=R)ask_ans(mid+1,right,L,R,a[x].rson,(ss+a[x].tag)%mod);
}
其中ss
为父节点及其根节点传下的tag之和,ans
统计答案
总结
可持久化数据结构的根本思路就是不断开点,然后动态维护它以及它的历史版本
注意好空间,不要吝啬!!!血的教训!