• 数据结构之主席树


    这里讲静态的主席树,关于静态区间第k小。(有兴趣的朋友还可以去看看我写的整体二分,代码实现略优于主席树我觉得,当然静态主席树是很好写的)

    题目描述:

    题目描述

    如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。

    输入输出格式

    输入格式:

     

    第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。

    第二行包含N个正整数,表示这个序列各项的数字。

    接下来M行每行包含三个整数 l, r, kl,r,k , 表示查询区间 [l, r][l,r] 内的第k小值。

     

    输出格式:

     

    输出包含k行,每行1个正整数,依次表示每一次查询的结果

     

    输入输出样例

    输入样例#1: 
    5 5
    25957 6405 15770 26287 26465 
    2 2 1
    3 4 1
    4 5 1
    1 2 2
    4 4 1
    输出样例#1:
    6405
    15770
    26287
    25957
    26287

    那么我们明确主席树是个什么东西。它就是,可持久化线段树。
    首先我们不考虑主席树,而是对于这个区间第k小问题做一个分析。如果我们对每一个区间暴力快排,毋庸置疑,绝对炸上天,T到你想哭。
    于是聪明的前人发现了这个问题的一个特点,下面我来说说。
    先把每一个数值离散化,树中每一个值就即是权值也是排名了(离散化建议看看我之前写的离散化博客里的代码)。好吧我到现在还没有说明这个树到底是个什么玩意儿。

       发明者的原话:“对于原序列的每一个前缀[1···i]建立出一棵线段树维护值域上每个数出现的次数,则其树是可减的”

       可以加减的理由:主席树的每个节点保存的是一颗线段树,维护的区间信息,结构相同,因此具有可加减性(关键)

       是的,我们对于每一个前缀建一颗树,每一段区间维护的是当前区间的点的个数,注意这里的区间不是位置的区间,而是权值的区间,

       这就是为什么要离散化了。

       那么减可以干什么呢?注意这是个很重要的思想,前缀和。

       先考虑一个问题,如果每次询问的l都是数组一开头的位置1,是不是很容易维护?

       下面举一个例子:维护数组6 2 3 1 4 5。 对前缀1~6建树 (图难看。。。不打紧的咳咳),这个求第k小应该一目了然吧

     

    然而,区间第k小其实很容易,对于区间[l,r]而言,只需要在每个节点用前缀r的节点值减去前缀l-1的节点值就好了,其他的和上面是一样的

    下面附上一组大佬的图

    注意和我的数据是不一样的,他的是4 1 1 2 8 9 4 4 3

    然后读者按照我刚刚说的用前缀r的树的每一个节点减去对应的前缀l-1的树的节点得到一颗新的树就好

     

    好的,现在我们回到原来的问题,主席树。其实上面就是主席树,但是若是我们对于每一个前缀都暴力建一次树的话,在时间和空间上都不能接受。

    这时候我们注意到相邻的两棵树其实是很相似的,我们可以让这一棵树和上一棵树共用一些节点,从而达到减低空间和时间复杂度的效果。前缀每次向右一位,其实就是多插入了一个数值,新的

    树和上一棵树的唯一区别其实就是一条链上变了而已。

    事实证明,这种方法十分的有效。

    就像这样,在原来的基础上加上几个点

    下面我附上我的代码供大家研究,其实我认为代码更加好懂

    #include<bits/stdc++.h>
    using namespace std;
    
    const int maxn=2e5+15;
    int n,m,cnt;
    int a[maxn],b[maxn],tree[maxn<<5],L[maxn<<5],R[maxn<<5],sum[maxn<<5];
    int build(int l,int r)
    {
    	int rt=++cnt;
    	sum[rt]=0;
    	if (l<r)
    	{
    		int mid=(l+r)>>1;
    		L[rt]=build(l,mid);
    		R[rt]=build(mid+1,r);
    	}
    	return rt;
    }
    int update(int pre,int l,int r,int x)
    {
    	int rt=++cnt;
    	L[rt]=L[pre];R[rt]=R[pre];sum[rt]=sum[pre]+1;//多插入了点于是加个1 
    	if (l<r)
    	{
    		int mid=(l+r)>>1;
    		if (x<=mid) L[rt]=update(L[pre],l,mid,x);//看看插到哪一边,另一边其实是一样的 
    		else R[rt]=update(R[pre],mid+1,r,x);
    	}
    	return rt;
    }
    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]];//减一下就好 
    	int mid=(l+r)>>1;
    	if (x>=k) return query(L[u],L[v],l,mid,k);
    	else return query(R[u],R[v],mid+1,r,k-x);
    }
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for (int i=1;i<=n;i++)
    	{
    		scanf("%d",&a[i]);
    		b[i]=a[i];
    	}
    	sort(b+1,b+1+n);//离散化 
    	int q=unique(b+1,b+1+n)-b-1;
    	tree[0]=build(1,q);
    	for (int i=1;i<=n;i++)
    	{
    		int t=lower_bound(b+1,b+1+q,a[i])-b; 
    		tree[i]=update(tree[i-1],1,q,t);//通过上一个树建树 
    	}
    	while (m--)
    	{
    		int x,y,z;
    		scanf("%d%d%d",&x,&y,&z);
    		int t=query(tree[x-1],tree[y],1,q,z);//通过x-1树和y树相减 
    		printf("%d
    ",b[t]);
    	}
    	return 0;
    }
    

      上文部分图来自大佬 Lpy_Now,感谢大佬

  • 相关阅读:
    复制表结构及数据
    mysql 字段名是关键字 报错
    mysql 截取字符串
    《官方资料》 例如:string 函数 、分组函数
    mysql event 入门
    Spring国际化
    Python学习记录
    精选股文
    为VS定制一个自己的代码生成器
    房产常识
  • 原文地址:https://www.cnblogs.com/xxzh/p/9158819.html
Copyright © 2020-2023  润新知