[jzoj 3379] 查询 {主席树}

题目

Description
对于一个整数序列,查询区间第k大数可以在O(logN)的时间内轻松完成。现在我们对这个问题进行推广。

考虑带重复数的集合(multiset)。定义在该类集合上的并操作“+”为两个集合的所有数不剔除重复得到的结果。比如,若A={1,2,2,3},B={2,3,4,4},则C={1,2,2,2,3,3,4,4}。

对于一个给定序列A[1…N],定义A[x…y]为包含y-x+1个元素的集合{A[x],A[x+1],…,A[y]}。现在,给定整数序列A,你需要回答很多询问,其中第i个询问要求集合A[x[i,1]…y[i,1]]+A[x[i,2]…y[i,2]]+…+A[x[i,ki]…y[i,ki]]中第pi小的元素。

Input
输入的第一行包含两个整数N和M,分别表示整数序列的长度和询问的个数。

第二行N个整数给出整数序列A。

第3到第M+2行给出了所有的询问。第i+2行前两个整数为ki和pi,接下来2ki个整数给出x[i, 1], y[i, 1], x[i,2], …, x[i, ki], y[i, ki]。这些数按照题目中的定义描述了第i个询问。

Output
对于每一个询问,输出相应的结果,即从小到大排序后的第pi个元素。


解题思路

把这道题目的查询修改即可,虽然zyc大佬说这很esay,但是我仍然理解了很久,可能是因为对主席树不太了解吧。

本来题目给出了几个 ( < = 5 ) (<=5) (<=5)的区间,我们可以将这些区间扔到主席树里面,分开求每个区间的值,然后累计起来,判断后继续分治。


代码

#include
#include
#include
#define mid ((l+r)>>1)
#define rep(i,x,y) for(register int i=x;i<=y;i++)
using namespace std; 
const int N=200010; 
int n,q,m,cnt=0,a[N],b[N],t[N],sum[N<<5],L[N<<5],R[N<<5],xt[N],yt[N],z; 
inline int build(int l,int r){
	int rt=++cnt; sum[rt]=0; 
	if (l<r) L[rt]=build(l,mid),R[rt]=build(mid+1,r); 
	return rt; 
}
inline 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; 
	if (l<r) {if (x<=mid) L[rt]=update(L[pre],l,mid,x); else R[rt]=update(R[pre],mid+1,r,x);}
	return rt; 
}
inline int query(int u[],int v[],int l,int r,int k){
	if (l==r) return l; int g=0; 
	rep(i,1,z) g+=sum[L[v[i]]]-sum[L[u[i]]]; 
	if (g>=k){
		rep(i,1,z) v[i]=L[v[i]],u[i]=L[u[i]]; query(u,v,l,mid,k); 
	} else {
		rep(i,1,z) v[i]=R[v[i]],u[i]=R[u[i]]; query(u,v,mid+1,r,k-g);
	}
}
int main(){
	scanf("%d%d",&n,&q); 
	rep(i,1,n) {scanf("%d",&a[i]); b[i]=a[i];}
	sort(b+1,b+n+1); m=unique(b+1,b+n+1)-b-1; 
	t[0]=build(1,m); 
	rep(i,1,n){
		int k=lower_bound(b+1,b+m+1,a[i])-b; 
		t[i]=update(t[i-1],1,m,k); 
	}
	int k; 
	while(q--){
		scanf("%d%d",&z,&k);
		rep(i,1,z) scanf("%d%d",&xt[i],&yt[i]),xt[i]=t[xt[i]-1],yt[i]=t[yt[i]]; 
		printf("%d\n",b[query(xt,yt,1,m,k)]); 
	} 
}

你可能感兴趣的:(可持久化线段树(主席树))