zoukankan      html  css  js  c++  java
  • 主席树

    主席树

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

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

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

    #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);
        }
    }
    
  • 相关阅读:
    css3的clip-path方法剪裁实现
    vue-cli3.0之vue.config.js的配置项(注解)
    用Canvas实现一些简单的图片滤镜
    转《图像处理之表面滤波》
    vue-axios的application/x-www-form-urlencod的post请求无法解析参数
    如何在linux中执行一个脚本
    列表、字典、元组小练习
    开发脚本自动部署及监控
    固化命令的方式、sed文本处理工具
    nginx服务、nginx反向代理和nfs共享服务
  • 原文地址:https://www.cnblogs.com/ucprer/p/11368517.html
Copyright © 2011-2022 走看看