P3834 【模板】可持久化线段树 2(主席树)题解 Link Solve Code

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

Solve

这道题有很多种做法,我用的是离线分治算法

设序列(A)中最小的数为(MINA),最大的数为(MAXA),我们尝试在值域([MINA,MAXA])上进行二分答案,设第一次二分的值为(mid).

在二分的过程中,扫描每个询问,统计在下标区间([l,r])中不大于(mid)的数有多少个,记为(c_i),对于每个([l_i,r_i]),(mid)都是一个固定的值,所以可以用树状数组来线性的求出每个(c_i)

然后我们对询问进行分类:

1.若(k_i≤c_i)则说明第(i)个询问的答案在([MINA,mid])中。

2.若(k_i>c_i),则说明第(i)个询问的答案在([mid+1,MAXA])中,并且等价于在值域([mid+1,MAXA])中的限制下找第(k_i-c_i)小的数。

我们把序列(A)中的数也进行分类,不大于(mid)的数构成一个子序列(LA),大于(mid)的数构成一个子序列(RA)

于是我们就可以把询问和数值分成两类,并且两类互不相关。

我们就得到了一个分治算法,设(solve(L,R,a,q)),表示有一个值域为([L,R])的整数序列(a),询问序列(q)(q)中存储个若干个三元组(l_i,r_i,k_i),表示求(a)的下标区间([l_i,r_i])中第(k_i)小的数。

1.设(mid=(L+R)>>1)

2.利用树状数组,对于(q)中每个询问,统计在序列(a)下标区间([l_i,r_i])中不大于(mid)的数有多少个,记为(c_i).

3.若(k_i≤c_i),则把该询问加入到(lq)中,否则令(k_i-=c_i),将其加入到序列(rq)中。

4.令序列(a)(≤mid)的数构成序列(la)(>mid)的数构成序列(ra)

5.递归求解(solve(L,mid,la,lq))(solve(mid+1,R,ra,rq))

递归的边界为:当(q)为空时,直接返回。当(L==R)时,说明询问序列中的每个问题L就是答案(R也一样),统计后返回

在实际做的时候我们把(lq)(la)放在一个序列里面做,打一个(op)标记判断一下,因为(la,lq)(ra,rq)是互不干扰的。合并序列时直接在原序列上修改,然后记下范围指针就好了

因为分治最多进行(logSIZE)层((SIZE)为值域大小),在算上树状数组的(log),整个事件复杂度为(O((N+M)logSIZElogN)),如果加上离散化,可以做到(O((N+M)log^2N))

Code

#include<bits/stdc++.h>
using namespace std;
int N,M,tot;
const int maxn=200005,INF=1<<30;
struct AS{
	int x,y,z,op;
}q[maxn<<1],lq[maxn<<1],rq[maxn<<1];
int ans[maxn],c[maxn];

inline int read(){
	int ret=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
	while(ch<='9'&&ch>='0')ret=ret*10+ch-'0',ch=getchar();
	return ret*f;
}

inline void add_x(int x,int val){
	for(int i=x;i<=N;i+=i&-i)c[i]+=val;
	return ;
}

inline int get(int x){
	int ret=0;
	for(int i=x;i;i-=i&-i)ret+=c[i];
	return ret;
}

void solve(int lval,int rval,int st,int ed){
	if(st>ed)return ;
	if(lval==rval){
		for(int i=st;i<=ed;i++){
			if(q[i].op>0)ans[q[i].op]=lval;
		}
		return ;
	}
	int mid=lval+rval>>1;
	int lt=0,rt=0;
	for(int i=st;i<=ed;i++){
		if(q[i].op==0){
			if(q[i].y<=mid)add_x(q[i].x,1),lq[++lt]=q[i];
			else rq[++rt]=q[i];
		}
		else {
			int cnt=get(q[i].y)-get(q[i].x-1);
			if(q[i].z<=cnt)lq[++lt]=q[i];
			else q[i].z-=cnt,rq[++rt]=q[i];
		}
	}
	for(int i=ed;i>=st;i--){
		if(q[i].op==0&&q[i].y<=mid)add_x(q[i].x,-1);
	}
	for(int i=1;i<=lt;i++)q[st+i-1]=lq[i];
	for(int i=1;i<=rt;i++)q[st+lt+i-1]=rq[i];
	solve(lval,mid,st,st+lt-1);
	solve(mid+1,rval,st+lt,ed);
	return ;
}
int main(){
	N=read();M=read();
	for(int i=1;i<=N;i++){
		++tot;
		q[tot].op=0;q[tot].x=i;q[tot].y=read();
	}
	for(int i=1;i<=M;i++){
		++tot;
		q[tot].op=i;q[tot].x=read(),q[tot].y=read(),q[tot].z=read();
	}
	solve(-INF,INF,1,tot);
	for(int i=1;i<=M;i++)printf("%d
",ans[i]);
	return 0;
}