1. 程式人生 > 實用技巧 >《演算法競賽進階指南》0x47離線分治演算法 基於值域的整體分治求解區間第K小

《演算法競賽進階指南》0x47離線分治演算法 基於值域的整體分治求解區間第K小

題目連結:https://www.acwing.com/problem/content/description/257/

基於值域的整體分治策略是給定mid,將值小於mid的數的下標放入lq,另外的放入rq,將詢問也進行分割,變成兩個獨立的子問題,對子問題進行求解即可。注意樹狀陣列每次結束一次solve後進入下一次solve函式之前必須是空的,保證空間充分利用。還有,可以充分利用原來分空間,將更改後的lq,rq再複製回[st,ed]區間中進行下一輪計算,在solve中指定可用區間,因為分治的兩個子問題是不相交地,時間複雜度約為O(nlog^2n)。

程式碼:

#include<iostream>
#include
<cstdio> #include<algorithm> using namespace std; const int maxn = 100010; const int inf = 1e9; struct node{ int op,x,y,z; }q[maxn<<1],lq[maxn<<1],rq[maxn<<1]; int ans[maxn]; int c[maxn]; int n,m; void update(int x,int y){ while(x<=n){//存的是下標 c[x]+=y; x
+=x&-x; } } int query(int x){ int ans=0; while(x){ ans+=c[x]; x-=x&-x; } return ans; } 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 lt=0,rt=0; int mid=(lval+rval)>>1; for(int i=st;i<=ed;i++){ if(q[i].op==0){//所有的插入操作都在查詢操作之後進行,對插入進行分割 if(q[i].y<=mid)update(q[i].x,1),lq[++lt]=q[i]; else rq[++rt]=q[i]; }else{//對查詢進行分割 int tmp=query(q[i].y)-query(q[i].x-1); if(tmp>=q[i].z)lq[++lt]=q[i];//在[lval,mid]中能找到第k小 else q[i].z-=tmp,rq[++rt]=q[i];//在[mid+1,rval]中找第k-tmp小的數 } } for(int i=st;i<=ed;i++){//樹狀陣列復原 if(q[i].op==0 && q[i].y<=mid)update(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); } int main(){ cin>>n>>m; int t=0; for(int i=1;i<=n;i++){//詢問之前記錄查詢 int val;scanf("%d",&val); q[++t].op=0;q[t].x=i,q[t].y=val; } for(int i=1;i<=m;i++){//記錄詢問 int l,r,k; scanf("%d%d%d",&l,&r,&k); q[++t].op=i; q[t].x=l,q[t].y=r,q[t].z=k; } solve(-inf,inf,1,t);//值域上的整體分治 for (int i=1;i<=m;i++)printf("%d\n",ans[i]); }