1. 程式人生 > >理解整體二分[例題[Dynamic Rankings]]

理解整體二分[例題[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;
}