如果有m次修改,就会存在m+1个版本
如果某一个结点的信息发生的变化,就会创造一个全新的结点出来
struct{ int l,r;//表示左右子节点的下标,而不是表示其区间的左右端点 int cnt;//当前区间中一共有多少个数 };
可持久化线段树难以进行区间修改的操作。因为它难以处理懒标记,因为有很多版本。
主席树中每个版本的结点个数一样,所以比较容易一点。
若是修改了一条路径,就创建条修改后的路径。
修改了左边红色路径上的信息,那么就重新创建右边这样路径的修改后的信息。红色的横线连接的两个相同意义的点。绿色的线连接的是修改后的点和原先并未修改的子结点
按照数值而不是下标上建立线段树,维护每个数值区间中一共有多少个数
如何求整体的第k小数:mid=1+n>>1,如果[1,mid]之间的数的个数cnt>=k,那么就递归左边,否则递归右边
如何求[1,R](原序列的下标)中的第k小数:在可持久化线段树(从左往右,给出一个点就更新一个版本)中只需要找到第R个版本,然后搜这个版本的第k小数就可以了
如和求[L,R](原序列的下标)中的第k小数:
在第R个版本中[l,r](数值)的个数是cnt1,在l-1版本中[l,r](数值)的个数是cnt2.cnt1-cnt2就是[L,R](原序列区间)中[l,r](数值)中的数的个数
一个典型的例题:https://www.acwing.com/problem/content/description/257/
题目描述:
给定长度为N的整数序列A,下标为 1∼N。
现在要执行M次操作,其中第i次操作为给出三个整数li,ri,ki,求A[li],A[li+1],…,A[ri][(即A的下标区间[li,ri中]第ki小的数是多少。
输入格式
第一行包含两个整数N和M。
第二行包含N个整数,表示整数序列A。
接下来M行,每行包含三个整数li,ri,ki用以描述第i次操作。
输出格式
对于每次操作输出一个结果,表示在该次操作中,第k小的数的数值。
每个结果占一行。
数据范围
N≤1e5,M≤1e4,|A[i]|≤1e9
AC代码(主席树模板代码):
#include<bits/stdc++.h> using namespace std; const int N=1e5+10,M=1e4+10; struct node{ int l,r;//表示左右子节点的空间下标,而不是表示其区间的左右端点 int cnt;//表示该数值区间中有多少个数 }tr[4*N+17*N];//线段树第0版本的空间+N(修改次数、版本空间)*logn int root[N],id=0,a[N];//root每个版本的头指针;id每增加一个结点的空间下标 vector<int>all; void pushup(int u){//更新结点信息 tr[u].cnt=tr[tr[u].l].cnt+tr[tr[u].r].cnt; } int build(int l,int r){//创建一颗树。也就是最初版本的线段树。l,r是离散化后的数值的下标 int p=id++;//id是还未使用的数组的第一个下标,创建的结点的空间下标就是此下标 if(l==r) {//当l==r时,表示到达了叶子节点 tr[p].cnt=0; return p; } int mid=l+r>>1; tr[p].l=build(l,mid);//左右递归创建线段树 tr[p].r=build(mid+1,r); return p; } int find(int x){//二分搜索,查找离散化后的下标 return lower_bound(all.begin(),all.end(),x)-all.begin(); } int insert(int p,int l,int r,int x){//插入一个数值,也就是更新一个版本。p是上一个版本对应的下标 int q=id++; tr[q]=tr[p];//首先将上一个版本的对应结点的信息复制过来 if(l==r){//说明到达了需要插入的结点的位置 tr[q].cnt++; return q; } int mid=l+r>>1;//二分插入 if(x<=mid) tr[q].l=insert(tr[p].l,l,mid,x); else tr[q].r=insert(tr[p].r,mid+1,r,x); pushup(q);//向上更新 return q; } int query(int q,int p,int l,int r,int k){ if(l==r) return l;//表明到达了要查询的第k小结点 int cnt=tr[tr[q].l].cnt-tr[tr[p].l].cnt;//判断在[L,R]区间内有多少给定的区间内的数 int mid=l+r>>1;//二分 if(k>cnt) return query(tr[q].r,tr[p].r,mid+1,r,k-cnt);//递归查询 else return query(tr[q].l,tr[p].l,l,mid,k); } int main(){ int n,m;scanf("%d%d",&n,&m); for(int i=0;i<n;i++){ scanf("%d",&a[i]); all.push_back(a[i]); } //将数值进行离散化 sort(all.begin(),all.end()); all.erase(unique(all.begin(),all.end()),all.end()); root[0]=build(0,all.size()-1);//0,all.size()-1是数值端点离散化之后的左右端点 for(int i=0;i<n;i++) root[i+1]=insert(root[i],0,all.size()-1,find(a[i]));//创建多个版本 int l,r,k; while(m--){ scanf("%d%d%d",&l,&r,&k); cout<<all[query(root[r],root[l-1],0,all.size()-1,k)]<<endl; } return 0; }
写于:2020/9/1 19:23