SP3946 MKTHNUM - K-th Number 题解
Link
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;
}