可持久化权值线段树(主席树)笔记
区别于普通线段树,权值线段树维护的信息不同
- 普通线段树:节点区间是序列的下标区间,维护区间最值,区间和等信息
- 权值线段树:节点区间是序列的值域,维护值域内数出现的次数
*图片引自董晓算法
给定一个区间,询问该区间内的第 \(k\) 小值是多少,暴力的方案就是每次都开一颗线段树,由于空间受限我们并不能这样做
一种可行且有效的办法就是建立主席树,保存每次插入时的历史版本,下一次直接从这个版本上衍生节点即可
#define lc(x) tr[x].ch[0]
#define rc(x) tr[x].ch[1]struct node{int ch[2];//左右儿子int s;//记录值域内的元素个数
};int n,m;
int a[N];
node tr[N*22];
int root[N],idx;
首先是建树过程,与线段树类似,递归创建值域 \([1,n]\) 内的各个节点,以及对节点进行编号
build(root[0],1,n);void build(int &x,int l,int r){x=++idx;//更新编号if (l==r) return;//递归出口int m=l+r>>1;build(lc(x),l,m);//递归建立左子树build(rc(x),m+1,r);//递归建立右子树
}
需要对 \(x\) 传入引用,即进行写入操作,更改节点原有值
插入操作,建立主席树,递归创建各个版本的线段树
insert(root[i-1],root[i],1,n,v);void insert(int x,int &y,int l,int r,int v){//插入vy=++idx;//对节点进行编号tr[y]=tr[x];//复制上个版本的信息到当前版本tr[y].s++;//因为插入了一个数,所以该值域内的元素个数加1if (l==r) return;int m=l+r>>1;//二分if (v<=m) insert(lc(x),lc(y),l,m,v);//依然传入上个版本的儿子与当前版本的引用else insert(rc(x),rc(y),m+1,r,v);
}
x
传入的是上一个版本的线段树的内容,不引用进行只读操作,y
传入的是当前版本来进行建树,写入操作
同样的 lc(x)
与 lc(y)
相当于两个指针,前者指向上一个版本,后者指向当前版本,进行同步搜索
查询操作,查询区间 \([l,r]\) 内的第 \(k\) 小
考虑普通情况:查询区间 \([1,r]\) 内的第 \(k\) 小,那么找到插入 \(r\) 时的版本在树上进行搜索即可
那么求 \([l,r]\) 上的第 \(k\) 小,可以采取类似前缀和的处理方式,分别查询 \([1,l-1]\) 与 \([1,r]\) 的信息,做差即可
query(root[l-1],root[r],1,n,k);int query(int x,int y,int l,int r,int k){if (l==r) return l;int m=l+r>>1;int s=tr[lc(y)].s-tr[lc(x)].s;if (k<=s) return query(lc(x),lc(y),l,m,k);else return query(rc(x),rc(y),m+1,r,k-s);//减去左子树值域内增加元素的个数
}
其中 int s=tr[lc(y)].s-tr[lc(x)].s;
计算的是两个版本节点对应左子树值域内元素的增加个数
如果左子树值域内增加的个数小于等于我们需要求的第 \(k\) 小,那么这个元素必然在左子树值域内增加的元素中
如果左子树值域内增加的个数大于我们需要求的第 \(k\) 小,那么这个元素必然在右子树值域内增加的元素中
考虑需要处理的数值值域较大但数量较少时,可以采取离散化的操作,一一映射下标
vector<int> v;for (int i=1;i<=n;i++){cin>>a[i];v.push_back(a[i]);
}
sort(v.begin(),v.end());//从小到大排序
v.erase(unique(v.begin(),v.end()),v.end());//去重int getid(int x){//得到离散化后的值,一一映射关系return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
主席树查询区间内小于等于 \(k\) 的元素个数
int rankquery(int x,int y,int l,int r,int k){if (r<=k) return tr[y].s-tr[x].s;//如果k比区间内所有值都要大,那么直接返回区间内元素总数if (l>k) return 0;//如果k比区间内所有值都要小,那么直接返回区间内元素总数int m=l+r>>1;//二分return rankquery(lc(x),lc(y),l,m,k)+rankquery(rc(x),rc(y),m+1,r,k);//递归左右子树搜索
}