理解整體二分[例題[Dynamic Rankings]]
[整體二分的概述]
大家應該知道怎麼一組找靜態區間第k大
二分答案出mid,如果比mid小的數的個數>k , mid變小,否則mid變大
當有很多組時,複雜度很不優秀,因此出現了整體二分
即將所有操作一起二分答案
剛剛理解整體二分時,立馬想到了一幅圖,與大家分享一下
想必大家看過吧
整體二分就是這麼一個過程
很多詢問與修改(不同顏色的球) 通過很多層篩選(遞迴二分) 最終到了自己顏色的那個區間(找到了答案)
而普通的二分可以理解為我們把球一個一個丟進去,等那個球找到了顏色再丟下一個,比把球一起倒進去慢了很多
進一步觀察,我們可以抽象地把圖理解為
[例題]
給定一個含有n個數的序列a[1],a[2],a[3]……a[n],程式必須回答這樣的詢問:對於給定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的數是多少(1≤k≤j-i+1),並且,你可以改變一些a[i]的值,改變後,程式還能針對改變後的a繼續回答上面的問題。你需要編一個這樣的程式,從輸入檔案中讀入序列a,然後讀入一系列的指令,包括詢問指令和修改指令。
對於每一個詢問指令,你必須輸出正確的回答。
輸入格式:
第一行有兩個正整數n(1≤n≤10000),m(1≤m≤10000)。分別表示序列的長度和指令的個數。
第二行有n個數,表示a[1],a[2]……a[n],這些數都小於10^9。接下來的m行描述每條指令,每行的格式是下面兩種格式中的一種。 Q i j k 或者 C i t
-
Q i j k (i,j,k是數字,1≤i≤j≤n, 1≤k≤j-i+1)表示詢問指令,詢問a[i],a[i+1]……a[j]中第k小的數。
-
C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改變成為t。
輸出格式:
對於每一次詢問,你都需要輸出他的答案,每一個輸出佔單獨的一行。
輸入樣例#1:
5 3 3 2 1 4 7 Q 1 4 3 C 2 6 Q 2 5 3
輸出樣例#1:
3 6
[具體做法]
int mid=(l+r)>>1; for(int i=L;i<=R;i++){ if(q[i].op==1 && q[i].y<=mid) add(q[i].x,1); if(q[i].op==2 && q[i].y<=mid) add(q[i].x,-1); if(q[i].op==3) tmp[i]=quary(q[i].y)-quary(q[i].x-1);//這個區間比mid小的個數 }
我們這裡要求對於每一個詢問(op==3) 的區間中,小於mid的數有多少
所以op==1或2 且他們的值<mid 就對於詢問有貢獻,加入樹狀陣列
在處理完tmp過後,要清空樹狀陣列
for(int i=L;i<=R;i++){//清空樹狀陣列
if(q[i].op==1 && q[i].y<=mid) add(q[i].x,-1);
if(q[i].op==2 && q[i].y<=mid) add(q[i].x,1);
}
接下來,就要把這些詢問和修改分成兩段
int cnt=0;
for(int i=L;i<=R;i++){
if(q[i].op==3){
if(q[i].cur+tmp[i]>=q[i].k) mark[i]=1,cnt++;//放在左邊
else q[i].cur+=tmp[i],mark[i]=0;
}
else{
if(q[i].y<=mid) mark[i]=1,cnt++;
else mark[i]=0;
}
}
q[i].cur 表示已經有多少個數比這個Mid詢問小
對於詢問,只要q[i].cur+比它當前Mid小的個數>=要查詢的K
那麼放在左邊
對於操作,只要值小於Mid也放在左邊,於是分成了兩部分
這兩部分再重複操作,就一直分下去了
#include<bits/stdc++.h>
#define N 500005
using namespace std;
int n,m,a[N],ans[N],tot,sign;
int tmp[N],c[N],mark[N];
struct Query{int x,y,op,cur,k,id;}q[N],ret[N];
int read(){
int cnt=0;char ch=0;
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))cnt=cnt*10+(ch-'0'),ch=getchar();
return cnt;
}
void add(int x,int val){
for(;x<=n;x+=x&-x) c[x]+=val;
}
int quary(int x){
int ans=0;
for(;x;x-=x&-x) ans+=c[x];
return ans;
}
void Solve(int L,int R,int l,int r){//L,R為詢問的區間,lr用來二分
if(L>R) return;
if(l==r){
for(int i=L;i<=R;i++) ans[q[i].id]=l;
return;
}
int mid=(l+r)>>1;
for(int i=L;i<=R;i++){
if(q[i].op==1 && q[i].y<=mid) add(q[i].x,1);
if(q[i].op==2 && q[i].y<=mid) add(q[i].x,-1);
if(q[i].op==3) tmp[i]=quary(q[i].y)-quary(q[i].x-1);//這個區間比mid小的個數
}
for(int i=L;i<=R;i++){//清空樹狀陣列
if(q[i].op==1 && q[i].y<=mid) add(q[i].x,-1);
if(q[i].op==2 && q[i].y<=mid) add(q[i].x,1);
}
int cnt=0;
for(int i=L;i<=R;i++){
if(q[i].op==3){
if(q[i].cur+tmp[i]>=q[i].k) mark[i]=1,cnt++;//放在左邊
else q[i].cur+=tmp[i],mark[i]=0;
}
else{
if(q[i].y<=mid) mark[i]=1,cnt++;
else mark[i]=0;
}
}
int l1=L,l2=L+cnt;
for(int i=L;i<=R;i++){
if(mark[i]) ret[l1++]=q[i];
else ret[l2++]=q[i];
}
for(int i=L;i<=R;i++) q[i]=ret[i];
Solve(L,l1-1,l,mid);
Solve(l1,l2-1,mid+1,r);
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
a[i]=read();
q[++tot].op=1,q[tot].x=i,q[tot].y=a[i];
}
while(m--){
char ch[3]; scanf("%s",ch);
if(ch[0]=='Q'){
int x=read(),y=read(),z=read();
q[++tot].op=3;
q[tot].x=x,q[tot].y=y;
q[tot].k=z,q[tot].id=++sign;
}
else{
int x=read(),y=read();
q[++tot].op=2,q[tot].x=x,q[tot].y=a[x];
q[++tot].op=1,q[tot].x=x,q[tot].y=y;
a[x]=y;
}
}
Solve(1,tot,0,1e9);
for(int i=1;i<=sign;i++)
printf("%d\n",ans[i]);
return 0;
}