• 主席树 从入门到入门


    前言:

    树剖板子早就打腻了。一直想学习一些新的模板,今天就学习一下主席树。
    其实是因为暑假集训的东西我全忘了

    1. luogu P3834 【模板】可持久化线段树 2(主席树)

    最板子的一道题,然而看了好长时间才看明白,震惊,我竟然折磨菜,活到爆!
    主席树经典用处:求静态区间第k大。
    先对a数组离散化,然后对a的每一个前缀开一个权值线段树,查询[l,r]的第k大时,需要线段树上二分,分别找到[1,r]中有几个大于mid,[1,l-1]中有多少大于mid,然后相减,就是[l,r]中大于mid的个数。
    代码:

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=200000+10;
    #define gc() (p1 == p2 ? (p2 = buf + fread(p1 = buf, 1, 1 << 20, stdin), p1 == p2 ? EOF : *p1++) : *p1++)
    #define read() ({ register int x = 0, f = 1; register char c = gc(); while(c < '0' || c > '9') { if (c == '-') f = -1; c = gc();} while(c >= '0' && c <= '9') x = x * 10 + (c & 15), c = gc(); f * x; })
    char buf[1 << 20], *p1, *p2;
    int a[maxn],b[maxn],root[maxn<<5];
    struct node{
    	int ls,rs,size;
    }tree[maxn<<5];
    int n,m,tot;
    bool cmp(int x,int y){
    	return x<y;
    }
    void update(int &u,int l,int r,int x){
    	int v=++tot;
    	tree[v]=tree[u];
    	u=v;
    	tree[v].size++;
    	if(l==r) return;
    	int mid=(l+r)>>1;
    	if(x<=mid) update(tree[u].ls,l,mid,x);
    	else update(tree[u].rs,mid+1,r,x);
    }
    int query(int s,int t,int k){
    	s=root[s];
    	t=root[t];
    	int mid,sum,l=1,r=b[0];
    	while(l<r){
    		mid=(l+r)>>1;
    		sum=tree[tree[t].ls].size-tree[tree[s].ls].size;
    		if(sum>=k){
    			s=tree[s].ls;
    			t=tree[t].ls;
    			r=mid;
    		}else{
    			k-=sum;
    			s=tree[s].rs;
    			t=tree[t].rs;
    			l=mid+1;
    		}
    	}
    	return l;
    }
    void Solve(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i){
    		scanf("%d",&a[i]);
    		b[i]=a[i];
    	}
    	sort(b+1,b+n+1,cmp);
    	b[0]=unique(b+1,b+n+1)-b-1;
    	root[0]=++tot;
    	for(int i=1;i<=n;++i){
    		root[i]=root[i-1];
    		int x=lower_bound(b+1,b+b[0]+1,a[i])-b;
    		update(root[i],1,b[0],x);
    	}
    	int l,r,k;
    	while(m--){
    		scanf("%d%d%d",&l,&r,&k);
    		printf("%d
    ",b[query(l-1,r,k)]);
    	}
    }
    int main(){
    	Solve();
    	return 0;
    }
    
    
    

    这里我在纠结一个问题,就是二分的过程。
    对于普通的二分答案,我一般是这么写的:

    ??

    while(l<=r){
    	int mid=(l+r)>>1;
    	if(check(mid)) r=mid-1;
    	else l=mid+1;
    }
    return l;
    

    但是搬到这里,他就不对了。

    int query(int s,int t,int k){//这是错误代码
    	s=root[s];
    	t=root[t];
    	int mid,sum,l=1,r=b[0];
    	while(l<=r){
    		mid=(l+r)>>1;
    		sum=tree[tree[t].ls].size-tree[tree[s].ls].size;
    		if(sum>=k){
    			s=tree[s].ls;
    			t=tree[t].ls;
    			r=mid-1;
    		}else{
    			k-=sum;
    			s=tree[s].rs;
    			t=tree[t].rs;
    			l=mid+1;
    		}
    	}
    	return l;
    }
    

    反正我就挺迷惑。
    后来张大仙给出了一个令我信服的解释。
    其实这压根不是二分答案,这个while循环模拟的是线段树上二分。
    换成递归版本应该是这样:

    inline int query(int u, int v, int l, int r, int k)//随便扒了一份题解上的代码
    {
        if (l >= r) return l;
        int x = sum[L[v]] - sum[L[u]];
        if (x >= k) return query(L[u], L[v], l, mid, k);
        else return query(R[u], R[v], mid+1, r, k-x);
    }
    

    现在就很清晰了。

  • 相关阅读:
    索引的优缺点
    php中创建和调用WebService
    Redis常用数据结构和操作
    PHP的array_merge
    数据库最左前缀原则
    Mysql+Sphinx实现全文搜索
    YAPI安装和使用
    根据导入xlxs的文件,来写入数据库
    操作RDS文档说明
    springboot查找配置文件路径的过程
  • 原文地址:https://www.cnblogs.com/wwcdcpyscc/p/13858673.html
Copyright © 2020-2023  润新知