zoukankan      html  css  js  c++  java
  • 【洛谷P3834】(模板)可持久化线段树 1(主席树)

    题目链接

    (模板)可持久化线段树 1(主席树)

    题目背景

    这是个非常经典的主席树入门题——静态区间第(k)
    数据已经过加强,请使用主席树。同时请注意常数优化

    题目描述

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

    输入格式

    第一行包含两个正整数(n,m),分别表示序列的长度和查询的个数。
    第二行包含(n)个整数,表示这个序列各项的数字。
    接下来(m)行每行包含三个整数(l,r,k),表示查询区间([l,r])内的第(k)小值。

    输出格式

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

    样例输入

    5 5
    25957 6405 15770 26287 26465 
    2 2 1
    3 4 1
    4 5 1
    1 2 2
    4 4 1
    

    样例输出

    6405
    15770
    26287
    25957
    26287
    

    说明/提示

    数据范围:

    对于(20\%)的数据满足:(1 leq n,m leq 10)
    对于(50\%)的数据满足:(1 leq n,m leq 10^3)
    对于(80\%)的数据满足:(1 leq n,m leq 10^5)
    对于(100\%)的数据满足:(1 leq n,m leq 2 imes 10^5)
    对于数列中的所有数(a_i),均满足(-{10}^9 leq a_i leq {10}^9)

    样例数据说明:

    (n=5),数列长度为(5),数列从第一项开始依次为([25957,6405,15770,26287,26465])
    第一次查询为([2,2])区间内的第一小值,即为(6405)
    第二次查询为([3,4])区间内的第一小值,即为(15770)
    第三次查询为([4,5])区间内的第一小值,即为(26287)
    第四次查询为([1,2])区间内的第二小值,即为(25957)
    第五次查询为([4,4])区间内的第一小值,即为(26287)

    题解

    题意:给一个序列有(m)个询问,求区间第(k)大。
    既然这是一道主席树板题,那么我就详细讲一下主席树吧。(当然还有其他方法过此题,但我也就只会打主席树了)
    众所周知,主席树是一种奇怪的线段树,结合了前缀和思想,那么我们考虑这样一种想法。
    我们用(n)棵线段树,第(i)棵线段树表示队列(1)~(i)的信息。
    我们用节点的(value)表示(1)~(i)中值在区间内的数的个数。
    但是这样的话区间就会很大,那么我们离散化一下就好了。
    那么我们就会得到(n)棵形状相同的树:(画画不是很好,尽请谅解)

    然后我们考虑一下如果第(i)棵树减去第(j)棵树(两棵树对应节点的(value)相减),那么我们得到的就是(i+1)~(j)这段区间的线段树了。
    所以我们每次新建一棵树的时候就要记录一下这棵树的根节点。
    这样我们就能在(O(logn))的时间复杂度下完成每个询问了,具体操作如下(感觉和平衡树求第(k)大差不多):
    从根节点开始搜索,如果(k)小于等于左子树的(value),那么第(k)大一定在左子树的区间内,往左边搜,
    否则的话就往右边搜,然后把(k)的值减去左子树的(value)
    当搜到区间内只有一个数(也就是区间的(left==right)),那么这个数就是第(k)大的数了。

    --------------------------------------------------------------------------优雅的分界线--------------------------------------------------------------------------
    **接下来是重点:** 如果按照上面说的方法去建树,那么会发现空间会超大,而且建图的时间复杂度就是$O(n^2)$了,那么接下来就是主席树的关键部分了。 我们考虑到当我们已经求完$1$~$i$的线段树后,思考一下和下一棵线段树相比,只是比这棵线段树多了一个点, 假设加入的数为$x$,那么对于树来说,只要点$(x,x)$的所有祖先的$value+1$就好了, 那么我们就想到对于那些不变的点,就不要新建一棵子树了,直接把下一棵数的点连到上一棵树上就好了,这样能节省很多空间(但是还是很费空间,所以用主席树前要想清楚空间是否够用)。 如下图所示: ![](https://i.postimg.cc/xT7F5jSy/image.png) 图中增加的数字是$2$,这样建图每次就只需要增加$logn$个点了,能节省很多空间和时间。

    个人认为理解了主席树的思想后实现就比较简单了。
    上代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,m;
    int a[200009],to[200009],ans[200009];
    struct bb{
    	int s,x;
    }b[200009];
    int rt[200009];
    int l,r,k;
    struct aa{
    	int l,r;
    	int s;
    	int cd[2];
    }p[5000009];
    int len;
    bool cmp(bb x,bb y){return x.s<y.s;}
    void init(int u,int l,int r){
    	p[u].l=l;p[u].r=r;
    	p[u].s=0;
    	if(l==r) return;
    	int mid=(l+r)/2;
    	if(l<=mid) init(p[u].cd[0]=++len,l,mid);
    	if(mid+1<=r) init(p[u].cd[1]=++len,mid+1,r);
    }
    void dfs(int u,int x){
    	p[u].s++;
    	if(p[u].l==p[u].r) return;
    	int mid=(p[u].l+p[u].r)/2;
    	len++;
    	if(x<=mid){
    		p[len]=p[p[u].cd[0]];
    		p[u].cd[0]=len;
    	}else{
    		p[len]=p[p[u].cd[1]];
    		p[u].cd[1]=len;
    	}
    	dfs(len,x);
    }
    void ask(int lu,int ru,int k){
    	if(p[lu].l==p[lu].r){
    		printf("%d
    ",ans[p[lu].l]);
    		return;
    	}
    	if(k<=p[p[ru].cd[0]].s-p[p[lu].cd[0]].s) ask(p[lu].cd[0],p[ru].cd[0],k);
    	else ask(p[lu].cd[1],p[ru].cd[1],k-(p[p[ru].cd[0]].s-p[p[lu].cd[0]].s));
    }
    int kk;
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int j=1;j<=n;j++){
    		scanf("%d",&a[j]);
    		b[j].s=a[j];
    		b[j].x=j;
    	}
    	sort(b+1,b+n+1,cmp);
    	for(int j=1;j<=n;j++){
    		if(j!=1 && b[j].x==b[j-1].x) k++;
    		to[b[j].x]=j-k;
    		ans[j-k]=b[j].s;
    	}
    	init(rt[0]=len=1,1,n-k);
    	for(int j=1;j<=n;j++){
    		rt[j]=++len;
    		p[len]=p[rt[j-1]];
    		dfs(len,to[j]);
    	}
    	for(int j=1;j<=m;j++){
    		scanf("%d%d%d",&l,&r,&k);
    		ask(rt[l-1],rt[r],k);
    	}
    	return 0;
    }
    
  • 相关阅读:
    POJ_1698_Alice's Chance
    透过比特币看移动互联网创业产品立项的关键要素
    localhost与127.0.0.1的区别
    web service初探
    Oracle导出DMP文件的两种方法
    html大小写问题
    浏览器端数据存储
    CSS滚动条样式设置
    浅谈forword和sendRedirect
    Poi对excel的基本操作
  • 原文地址:https://www.cnblogs.com/linjiale/p/12215267.html
Copyright © 2011-2022 走看看