【洛谷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\times 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\n",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;
}
posted @ 2020-01-19 19:15  oblivionl  阅读(158)  评论(0编辑  收藏  举报