zoukankan      html  css  js  c++  java
  • 权值线段树、可持久化线段树 以及 主席树 基础

    权值线段树与第Kth 大/小

    可持久化线段树解决历史信息记录问题

    权值线段树+可持久化 = 静态主席树

    权值线段树:

    权值线段树和常用的线段树区别在于,基础线段树维护(sum,min,max,xor)等值,而权值中结点位置表示的是该结点的值所对应的个数,从而维护区间的个数。由于题中给出的数不是连续的,有可能差值很大,所以我们要先进行离散化处理,然后再构造该数据结构。

    在查询总的第(Kth)时,我们可以通过判断当前(k)与右节点个数来寻找位置。如果小于等于右节点维护的区间中值出现的次数,则向右走,否则减去右节点的权值次数然后向左走,这样就能查询到区间(1)(n)的第(Kth).

    那我要求某个区间的第(Kth)大怎么搞?

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 1e5+5;
    struct node
    {
    	int num;
    }seg[maxn<<1];
    void push_up(int p){
    	seg[p].num = seg[p<<1].num + seg[p<<1|1].num;
    }
    void build(int l,int r,int p){
    	if(l==r){
    		//优先往左然后往右保证一次增大
    		scanf("%d",&seg[p].num);
    		return ;
    	}
    	int mid = l+r>>1;
    	build(l,mid,p<<1);
    	build(mid+1,r,p<<1|1);
    	push_up(p);
    }
    //递归查询:总的第k大 
    int query(int k,int l,int r,int p){
    	int mid = l+r>>1;
    	if(l==r){
    		return l;
    	}
    	if(k<=seg[p<<1|1].num) return query(k,mid+1,r,p<<1|1);
    	else return query(k-seg[p<<1|1].num,l,mid,p<<1);
    }
    int main(){
    	int n;
    	cin>>n;
    	build(1,n,1);
    	int m;
    	cin>>m;
    	for(int i=1;i<=m;i++){
    		int k; cin>>k;
    		cout<<"k:"<<query(k,1,n,1)<<endl;
    	}
    }
    

    上面的代码便是基于“传统”的线段树,建堆的方式搭的一棵树。左右结点直接通过 (p<<1)(p<<1|1) 既可以得到,既可以直接通过下标的倍增操作得到左右结点。

    要能够查询某个区间的第K大,我们还要保留历史信息,即使之成为可持久化的线段树,加入动态开点操作。

    动态开点,即 增加对某个结点左右儿子的记录 ,例如加入(lc,rcspace(leftspace child,rightspace child))信息

    这样我要寻找其左右儿子是通过 (seg[rt].lc) 或者 (seg[rt].rc) 得到 , 而不是 (seg[rt<<1]) 或者 (seg[rt<<1|1])

    关于每一个结点存的位置,我们可以直接顺序存放在数组中即可。用一个(cnt)来记录存放在数组的第几个位置即可

    我们知道在更新一个点时,其所有祖先结点都要更新一边。如果树深为(n),则最多新增(log^n)个结点,同时所有的点都会将根结点进行更新。所以我们用一个(root)数组代表每一次修改后的新增加结点链的根。

    例如下图,我们在更新4~4区间时,则会新增红色部分的链

    可持久化线段树 例题:洛谷P3919

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e6+10;
    int n,m,a[N],root[N];
    int cnt=0,lc[N<<6],rc[N<<6],val[N<<6];
    //动态建树函数 一般都会返回新建结点编号 
    int build(int l,int r) {
        int q=++cnt;  //动态建树 
        if (l==r) {
            lc[q]=rc[q]=0; val[q]=a[l];
            return q;
        }
        int mid=l+r>>1;
        lc[q]=build(l,mid);
        rc[q]=build(mid+1,r);
        return q;
    }
    
    //这个其实是插入函数,因为公用结点的缘故并不能修改版本信息导致其他版本受影响 
    int update(int p,int l,int r,int x,int v) {  //新版本线段树是基于版本p而来 
        int q=++cnt;  //动态建树
        if (l==r) {
            lc[q]=rc[q]=0; val[q]=v;
            return q;
        } 
        int mid=l+r>>1;
        lc[q]=lc[p]; rc[q]=rc[p]; val[q]=val[p];  //为了公用信息,先复制一份 
        if (x<=mid) lc[q]=update(lc[p],l,mid,x,v);
        if (x>mid) rc[q]=update(rc[p],mid+1,r,x,v);
        return q;  
    }
    
    int query(int p,int l,int r,int x) {  //查询版本p位置x的值 
        if (l==r) return val[p];
        int mid=l+r>>1;
        if (x<=mid) return query(lc[p],l,mid,x);
        if (x>mid) return query(rc[p],mid+1,r,x); 
    }
    
    int main()
    {
        cin>>n>>m;
        for (int i=1;i<=n;i++) scanf("%d",&a[i]);
        root[0]=build(1,n);
        
        for (int i=1;i<=m;i++) {
            int k,opt,x,v; scanf("%d%d",&k,&opt);
            if (opt==1) {
                scanf("%d%d",&x,&v);
                root[i]=update(root[k],1,n,x,v);
            } else {
                scanf("%d",&x);
                printf("%d
    ",query(root[k],1,n,x));
                root[i]=root[k];
            }
        }
        return 0;
    }
    

    主席树静态查询区间第K大 例题:洛谷P3834

    通过之间可持久化线段树对历史操作状态的记录,然后类似前缀和的思想,查询区间([L,R])的第(K)大,即是查询从(L到R)次操作新增部分中的第(K)大。

    如下图:一开始比如在第(4)次插入权值之后,为下面这一棵权值线段树。

    在第8次插入权值后,为下图状态

    也就是说,在([4,8])的操作区间中,新增加了(1个2),和(3个4) (这里的2,4表示离散化之后对应的原数组的第(2th)和第(4th))

    假如一开始n个数的序列为: 1 2 3 4 2 4 4 4 ,那么实际上查询([4,8])范围也就是查询新加入的部分 ({2,4,4,4})的第(Kth) ,而要得到新增加的有哪些部分,则可以用当前信息(第8次操作后)的权值线段树的值减去之前(第4次操作后的值),那么这时候我们查询这个([4,8])区间内的第K大,不就可以用当时权值线段树查询的方式去找了么?

    Code:

    #include <bits/stdc++.h>
    #define IOS ios::sync_with_stdio(0); cin.tie(0);
    using namespace std;
    const int maxn = 1e6+5;
    int cnt;
    int root[maxn];
    struct node{
        int val,lc,rc;
    }seg[maxn<<6];
    int a[maxn],b[maxn];
    int build(int l,int r){
    	int p = ++cnt;
    	if(l==r){
    		seg[p].val = 0;
    		seg[p].lc = seg[p].rc = 0;
    		return p;
    	}
    	int mid = l+r>>1;
    	seg[p].lc  = build(l,mid);
    	seg[p].rc  = build(mid+1,r);
    	return p;
    }
    void push_up(int p){
    	seg[p].val = seg[seg[p].lc].val + seg[seg[p].rc].val; //维护区间信息
    }
    int update(int q,int l,int r,int k){
    	int p = ++cnt;
    	if(l==r){
    		seg[p].val = seg[q].val + 1;
    		seg[p].lc = seg[p].rc = 0;
    		return p;
    	}
    	seg[p] = seg[q]; //赋值结点值
    	int mid =l+r>>1;
    	if(k<=mid) seg[p].lc = update(seg[q].lc,l,mid,k);
    	if(k>mid) seg[p].rc = update(seg[q].rc,mid+1,r,k);
    	push_up(p);
    	return p;
    }
    int query(int l,int r,int L,int R,int k){
    	if(l==r) return b[l];
    	int mid = l+r>>1;
    	int num = seg[seg[R].lc].val - seg[seg[L].lc].val;
    	if(k<=num) return query(l,mid,seg[L].lc,seg[R].lc,k);
    	if(k>num) return query(mid+1,r,seg[L].rc,seg[R].rc,k-num); 
    }
    int main(){
    	IOS
    	int n,m;
    	cin>>n>>m;
    	for(int i=1;i<=n;i++) cin>>a[i],b[i] = a[i];
    	sort(b+1,b+1+n);
    	int tot = unique(b+1,b+1+n) - (b+1);
    	cnt = 0; root[0] = build(1,n);//建空树
    	for(int i=1;i<=n;i++){
    		int x = lower_bound(b+1,b+1+tot,a[i]) - b;//最小为1
    		root[i] = update(root[i-1],1,tot,x);
    	}
    	for(int i=1;i<=m;i++){
    		int L,R,k;
    		cin>>L>>R>>k;
    		cout<<query(1,tot,root[L-1],root[R],k)<<endl;
    	}
    	return 0;
    }
    
  • 相关阅读:
    hdu 1108 最小公倍数
    hdu 1106 排序
    hdu 1097 A hard puzzle
    hdu 1076 An Easy Task
    hdu 1064 Financial Management
    hdu 1061 Rightmost Digit
    hdu 1050 Moving Tables
    hdu 1060 Leftmost Digit
    hdu 1049 Climbing Worm
    hdu1104
  • 原文地址:https://www.cnblogs.com/Tianwell/p/12839264.html
Copyright © 2011-2022 走看看