分塊
由於我在網上找不到定義,只好編一個。
分塊 是一種將問題分解成若干個子問題,逐項解決子問題后得到原問題答案的思想。
塊
考慮這樣一個問題。
你有一個序列 \(a[1...N]\),你需要寫一個數據結構維護它,支持以下操作:
- 修改 \(a[x]\) 的值;
- 查詢 \(a[L...R]\) 的最大值、和、按位與和、或和、異或和、與非和、或非和、異或非和。
事實上,這道題可以加上很多很多個操作,這樣 線段樹 也無能為力了。
我們不妨想想如何優化朴素程序。
以求和為例,朴素程序寫法如下:
for(int i=L;i<=R;++i)
ans+=a[i]; //flag
flag 行一共被執行了 \(R-L+1\) 次,這不是我們希望看到的。
如果是這樣寫呢?
for(int i=L;i<=R;i+=2)
ans+=a[i]+a[i+1]; //flag
if((R-L+1)&1) ans-=a[R+1];
不難想到,flag 行只被執行了原來次數的一半。
設 flag 行將連續的 \(d\) 個 \(a\) 累加給 \(ans\),則一共需要執行 flag 行 \(\frac{R-L+1}{d}\) 次。
如果我們用一個變量維護這 \(d\) 個數,那么我們查詢的時間復雜度就是 \(O(\frac{R-L+1}{d})\),修改操作的時間復雜度是 \(O(d)\)。
不妨令兩操作次數相同,則程序有最低復雜度時,$$\frac{R-L+1}{d}=d$$即$$d=\sqrt{R-L+1}$$
換言之,當 \(d\) 的值等於操作區間(一般是 \([1,N]\))的 \(\frac12\) 次方時,該程序的時間復雜度最低,是 \(O(N^\frac12)\)(單次執行)。
我們不妨將被一並處理的 \(d\) 個數視作一塊,那么原區間就被分成了 \(\sqrt N\) 塊。
舉個例子。我們現在要維護一個序列的和,分塊后每個塊的信息如下:
其中 \(a[i]\) 表示第 \(i\) 個塊內每個數的和。
至此, 分塊 已經介紹完畢了。下面我們看一道例題。
例題 您需要寫一種數據結構,維護一個有序數列,支持以下操作:
- 查詢 \(k\) 在區間內的排名;
- 查詢區間內排名為 \(k\) 的值;
- 修改某一位值上的數值;
- 查詢 \(k\) 在區間內的前驅(前驅定義為嚴格小於 \(x\),且最大的數);
- 查詢 \(k\) 在區間內的后繼(后繼定義為嚴格大於 \(x\),且最小的數)。
參考代碼
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int MAXN=50010;
const int MAXE=230;
const int MAXA=(int)1e8;
int n,m,sq;
int sx,sy,sd;
struct point{
int x,y;
friend bool operator<(const point a,const point b){
return a.y<b.y;
}
}a[MAXN];
struct split{
int l,r;
int mininum;
point e[MAXE];
void update(){
sort(e+1,e+(r-l+1)+1);
mininum=e[1].y;
}
void build(point p[MAXN],int L,int R){
if(R>n) R=n;
l=L;r=R;
for(int i=l;i<=r;++i)
e[i-l+1]=p[i];
update();
}
int query(int x){
int left=1,right=(r-l+1),mid,sum=0;
while(left<=right){
mid=(left+right)/2;
if(e[mid].y<x){
sum=mid;
left=mid+1;
}else
right=mid-1;
}
return sum;
}
int que(int left,int right,int x){
int cnt=0;
for(int i=l;i<=r;++i)
if(e[i-l+1].x>=max(l,left)&&e[i-l+1].x<=min(r,right)&&e[i-l+1].y<x) ++cnt;
return cnt;
}
int check(int left,int right,int x){
if(left<=l&&r<=right)
return mininum==x;
for(int i=max(l,left);i<=min(r,right);++i)
if(e[i-l+1].y==x) return 1;
return 0;
}
void change(int x,int d){
for(int i=1;i<=r-l+1;++i)
if(e[i].x==x){
e[i].y=d;
break;
}
update();
}
}s[MAXE];
int len=0;
int getnum(int x){
return (x-1)/sq+1;
}
int rank(int l,int r,int x){
int L=getnum(l),R=getnum(r);
int cnt=0;
for(int i=L+1;i<=R-1;++i)
cnt+=s[i].query(x);
cnt+=s[L].que(l,r,x);
if(L!=R) cnt+=s[R].que(l,r,x);
if(!cnt){
for(int i=L;i<=R;++i)
if(s[i].check(l,r,x))
return cnt+1;
return 1;
}
return cnt+1;
}
int find(int l,int r,int x){
int left=0,right=MAXA,mid,s=0;
while(left<=right){
mid=(left+right)/2;
if(rank(l,r,mid)<=x){
s=mid;
left=mid+1;
}else
right=mid-1;
}
return s;
}
void change(int x,int d){
s[getnum(x)].change(x,d);
}
int findupper(int l,int r,int x){
int rk=rank(l,r,x);
if(rk>r-l+1) return 2147483647;
bool flag=(find(l,r,rk)==x);
return find(l,r,rk+flag);
}
int findlower(int l,int r,int x){
int rk=rank(l,r,x);
if(rk<=1) return -2147483647;
return find(l,r,rk-1);
}
inline int read(){
int x=0; char c;
do c=getchar(); while(c<'0'||c>'9');
while(c>='0'&&c<='9')
x=x*10+c-48,c=getchar();
return x;
}
int main(){
n=read();m=read();sq=sqrt(n);
for(int i=1;i<=n;++i)
a[i]=(point){i,read()};
int cnt=0;
while(cnt<n){
s[++len].build(a,cnt+1,cnt+sq);
cnt+=sq;
}
for(int i=1;i<=m;++i){
sd=read();
switch(sd){
case 1:
sx=read();sy=read();sd=read();
int c;
printf("%d\n",c=rank(sx,sy,sd));
break;
case 2:
sx=read();sy=read();sd=read();
printf("%d\n",find(sx,sy,sd));
break;
case 3:
sx=read();sd=read();
change(sx,sd);
break;
case 4:
sx=read();sy=read();sd=read();
printf("%d\n",findlower(sx,sy,sd));
break;
case 5:
sx=read();sy=read();sd=read();
printf("%d\n",findupper(sx,sy,sd));
break;
default:
break;
}
}
}
下面列出了幾道難度較低的練習題,有興趣的讀者可自行嘗試。
練習 1 您需要設計一種數據結構,維護一個序列 \(a[1...N]\),支持以下操作:
- 將 \(a[x]\) 的值加 \(d\);
- 查詢 \(a[L...R]\) 中小於等於 \(d\) 的數的個數。
練習 2 您需要設計一種數據結構,維護一個序列 \(a[1...N]\),支持以下操作:
- 將 \(a[L...R]\) 的值加 \(d\);
- 查詢 \(a[L...R]\) 中小於 \(a[x]\) 的最大的數。
練習 3 (luogu P3372)您需要設計一種數據結構,維護一個序列 \(a[1...N]\),支持以下操作:
- 將 \(a[L...R]\) 的值加 \(d\);
- 查詢 \(a[L...R]\) 的數的和。
練習 4 您需要設計一種數據結構,維護一個序列 \(a[1...N]\),支持以下操作:
- 將 \(a[L...R]\) 的值開方(向下取整);
- 查詢 \(a[L...R]\) 的數的和。
練習 5 (luogu P3373)您需要設計一種數據結構,維護一個序列 \(a[1...N]\),支持以下操作:
- 將 \(a[L...R]\) 的值加 \(d\);
- 將 \(a[L...R]\) 的值乘 \(d\);
- 查詢 \(a[L...R]\) 的數的和。
練習 6 您需要設計一種數據結構,維護一個序列 \(a[1...N]\),支持以下操作:
- 查詢 \(a[L...R]\) 的數有多少個與 \(d\) 相等。將 \(a[L...R]\) 的每個數改成 \(d\)。