SP3946 MKTHNUM - K-th Number 题解

Link

SP3946 MKTHNUM - K-th Number

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(){
	freopen("SP3946.in","r",stdin);
	freopen("SP3946.out","w",stdout);
	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\n",ans[i]);
	return 0;
}
posted @ 2020-10-30 07:45  Martian148  阅读(103)  评论(0编辑  收藏  举报