主席树

主席树

可持续化线段树:在普通线段树的基础上,能询问/修改每个历史版本的线段树的信息(每次询问与修改都会产生一个新的版本)。

即每次更新都会产生一个新的树,由于是单点更新,因此只会有一条链发生改变,每次只要新加一条链即可,当然链上的节点一个儿子是旧的节点,一个儿子是新的节点。为了能够访问不同版本的线段树,需要记录每个版本的根节点。

可持续化线段树 单点修改单点查询(基本没啥用,别抄这个)

#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\n",query(root[ed_],1,n,p_) );
		}
	}
}

主席树-静态区间第k小

主席树最原始应用,建立在权值线段树上,从左往右遍历原数组,用数组值更新权值线段树(新链的每一个点都在旧节点的基础上+1),这样纵向地看不同版本的权值线段树的同一个节点,其实都是一个权值的前缀和,也就是说,New版本的节点权值-Old版本的节点权值,就等于原数组在[Old+1,New]区间上,在这个节点对应的[l,r]范围内的数有多少个。

由于线段树的节点个数限制(空间限制),原数组若数值范围较大,则需要先进行离散化的操作。

由于主席数的建立是沿着原数组逐个链更新的,因此主席树的区间查找也必然是通过离散化的值。前述的“原数组”:由于数组本就是一个离散化的概念,因此在查找的时候也很容易理解主席树中的"版本"与原数组的"区间"之间的对应关系。但如果原始数据是一堆二维点对,问$x\in [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\n",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\n",li);
    }
}
posted @ 2019-08-17 14:29  UCPRER  阅读(218)  评论(0编辑  收藏  举报