主席树
可持续化线段树:在普通线段树的基础上,能询问/修改每个历史版本的线段树的信息(每次询问与修改都会产生一个新的版本)。
即每次更新都会产生一个新的树,由于是单点更新,因此只会有一条链发生改变,每次只要新加一条链即可,当然链上的节点一个儿子是旧的节点,一个儿子是新的节点。为了能够访问不同版本的线段树,需要记录每个版本的根节点。
可持续化线段树 单点修改单点查询(基本没啥用,别抄这个)
#include <cstdio>
using namespace std;
const int maxn=2e7+5e6;
const int maxm=1e6+5;
int v[maxn],lson[maxn],rson[maxn];
int root[maxm]={1},a[maxm];//root[i]存i版本对应根节点(0号版本对应根为1)
int tot;
int build(int l,int r){
int pos=++tot;
if(l==r){
v[pos]=a[l];
return pos;
}
int mid=(l+r)>>1;
lson[pos]=build(l,mid);
rson[pos]=build(mid+1,r);
return pos;
}
int update(int rt,int l,int r,int p,int w){
int pos=++tot;
if(l==r){
v[pos]=w;
return pos;
}
lson[pos]=lson[rt];
rson[pos]=rson[rt];
int mid=(l+r)>>1;
if(p<=mid) lson[pos]=update(lson[rt],l,mid,p,w);
else rson[pos]=update(rson[rt],mid+1,r,p,w);
return pos;
}
int query(int rt,int l,int r,int p){
if(l==r) return v[rt];
int mid=(l+r)>>1;
if(p<=mid) return query(lson[rt],l,mid,p);
else return query(rson[rt],mid+1,r,p);
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build(1,n);
int ed_,type,p_,w_;
for(int i=1;i<=m;i++){
scanf("%d%d",&ed_,&type);
if(type==1){
scanf("%d%d",&p_,&w_);
root[i]=update(root[ed_],1,n,p_,w_);
}
if(type==2){
scanf("%d",&p_);
root[i]=root[ed_];
printf("%d
",query(root[ed_],1,n,p_) );
}
}
}
主席树-静态区间第k小
主席树最原始应用,建立在权值线段树上,从左往右遍历原数组,用数组值更新权值线段树(新链的每一个点都在旧节点的基础上+1),这样纵向地看不同版本的权值线段树的同一个节点,其实都是一个权值的前缀和,也就是说,New版本的节点权值-Old版本的节点权值,就等于原数组在[Old+1,New]区间上,在这个节点对应的[l,r]范围内的数有多少个。
由于线段树的节点个数限制(空间限制),原数组若数值范围较大,则需要先进行离散化的操作。
由于主席数的建立是沿着原数组逐个链更新的,因此主席树的区间查找也必然是通过离散化的值。前述的“原数组”:由于数组本就是一个离散化的概念,因此在查找的时候也很容易理解主席树中的"版本"与原数组的"区间"之间的对应关系。但如果原始数据是一堆二维点对,问$xin [l,r] $,其中y<=k的个数,或者第k小的y,那么不能直接建树,而要通过对x进行排序,从小到大建树,查询的时候x也是离散化的值。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=2e5+5;
const int Log=40;
int num[maxn*Log],lson[maxn*Log],rson[maxn*Log];
int root[maxn],a[maxn],b[maxn];
int tot;
int build(int l,int r){
int pos=++tot;
if(l<r){
int mid=(l+r)>>1;
lson[pos]=build(l,mid);
rson[pos]=build(mid+1,r);
}
return pos;
}
int update(int rt,int l,int r,int p){
int pos=++tot;
num[pos]=num[rt]+1;
lson[pos]=lson[rt];
rson[pos]=rson[rt];
if(l<r){
int mid=(l+r)>>1;
if(p<=mid) lson[pos]=update(lson[rt],l,mid,p);
else rson[pos]=update(rson[rt],mid+1,r,p);
}
return pos;
}
int query(int Old,int New,int l,int r,int k){
if(l==r)return l;
int mid=(l+r)>>1;
int x=num[lson[New]]-num[lson[Old]];
if(x>=k) return query(lson[Old],lson[New],l,mid,k);
else return query(rson[Old],rson[New],mid+1,r,k-x);
}
int main(){
int n,q;
cin>>n>>q;
for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
sort(b+1, b+1+n);
int size = unique(b+1, b+1+n)-b-1;
root[0] = build(1, size);
for(int i=1;i<=n;i++)
a[i] = lower_bound(b+1,b+1+size, a[i])-b;
for(int i=1;i<=n;i++)
root[i]=update(root[i-1],1,size,a[i]);
while(q--){
int x,y,z;scanf("%d%d%d",&x,&y,&z);
int p=query(root[x-1],root[y],1,size,z);
printf("%d
",b[p]);
}
}
静态区间内 大于等于k / 小于等于k 的个数
int query(int New,int Old,int l,int r,int k){//查区间内>=k的数的个数
if(l>=k){
return num[New]-num[Old];
}
int mid=(l+r)>>1;
int res=0;
if(mid>=k) res+=query(lson[New],lson[Old],l,mid,k);
if(r>=k) res+=query(rson[New],rson[Old],mid+1,r,k);
return res;
}
int query(int New,int Old,int l,int r,int k){//查区间内<=k的数的个数
if(r<=k){
return num[New]-num[Old];
}
int mid=(l+r)>>1;
int res=0;
if(l<=k) res+=query(lson[New],lson[Old],l,mid,k);
if(mid+1<=k) res+=query(rson[New],rson[Old],mid+1,r,k);
return res;
}
例题
Cutting Bamboos(主席树+二分)
题意
给你一些竹子,q个询问,问你从第l到第r个竹子,如果你要用y次砍完它,并且每次砍下来的长度是相同的,问你第x次砍在哪。
思路:
先求前缀和,(l,r)区间要砍y刀,每刀总长度step=(sum[r]-sum[l-1])/y,第x次砍完必定还剩下总长度为step*(y-x)的竹子。想到可以二分砍的高度,判断砍得偏高还是偏低,可以通过计算剩下得总长度比要求大还是小。设高度为h,则剩下的总长度=(高度小于h的竹子的高度和+高度大于h的竹子数量 * x) ,主席树可以维护区间上小于x的元素个数以及元素和,正好满足要求。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
const int N=2e5+2;
const int Log=40;
ll val[maxn*Log],sum[maxn];
int root[maxn],a[maxn],num[maxn*Log],lson[maxn*Log],rson[maxn*Log];
int tot;
double Sum,Num;
int build(int l,int r){
int root=++tot;
val[root]=0;
num[root]=0;
if(l<r){
int mid=(l+r)>>1;
lson[root]=build(l,mid);
rson[root]=build(mid+1,r);
}
return root;
}
int update(int pre,int l,int r,int x){
int root=++tot;
num[root]=num[pre]+1;
val[root]=val[pre]+x;
lson[root]=lson[pre];
rson[root]=rson[pre];
if(l<r){
int mid=(l+r)>>1;
if(x<=mid) lson[root]=update(lson[pre],l,mid,x);
else rson[root]=update(rson[pre],mid+1,r,x);
}
return root;
}
void query(int Old,int New,int l,int r,int k){//查区间内<=k的数的个数
if(l>k)return;
if(r<=k){
Sum+=val[New]-val[Old];
Num+=num[New]-num[Old];
return;
}
int mid=(l+r)>>1;
query(lson[Old],lson[New],l,mid,k);//函数开始会判断跳出,这里不需要判断
query(rson[Old],rson[New],mid+1,r,k);
}
int main(){
int n,q;
cin>>n>>q;
root[0]=build(1,n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
root[i]=update(root[i-1],1,n,a[i]);
}
while(q--){
int l,r,x,y;
scanf("%d%d%d%d",&l,&r,&x,&y);
double li=0,ri=100000,eps=1e-10;
double step=1.0*(sum[r]-sum[l-1])/y;
while(ri-li>eps){
double mid=(li+ri)/2;
Sum=0;Num=0;
query(root[l-1],root[r],1,n,(int)mid);
Num=(r-l+1)-Num;//比mid矮的数量转化为比mid高的数量
double temp=mid*Num+Sum;
if(step*(y-x)<temp)//砍高了
ri=mid;
else li=mid;
}
printf("%.15lf
",li);
}
}