Bloober Team曾有機會開發《電鋸驚魂》卻主動放棄
分塊是一種 思想
它用於存在區間問題,並且結合律不是簡單相加的時候
線段樹和樹狀陣列此時不可用,這個時候,我們就要使用分塊
切入正題
分塊思想是這樣的
設陣列長度是n,共有q塊,把陣列存下來,分成\(O(\frac{n}{q})\)段,每段記憶體一個tag表示區間標記,當區間修改的時候,對於整塊,直接增加標記即可。
很類似線段樹的"標記永久化",思想都是一樣的,不過實現方法有很大差別。
而對於不滿一塊的情況下,直接暴力運算即可。
使用條件
可以快速算出一個操作對單獨的數的影響。
塊與塊
一般情況下,n,m \(\leqslant\) 200000
當操作簡單或者資料水時,可以到500000
時間複雜度分析
當塊長為\(O(\frac{n}{q})\)
對於區間修改,在塊內的修改,顯然最多把n個數全都改了,時間複雜度O(q)
在單塊的暴力修改,最多修改兩個塊長-2,時間複雜度2*n/q-2=\(\frac{n}{q}\)
∴時間複雜度為O(n/q+q);由均值不等式知,當q=\(\sqrt{n}\)時,單次時間複雜度取最小值O(\(\sqrt{n}\)).
附加:對於基礎區間查詢
區間查詢:類似區間加法,暴力統計左右不完整塊的答案,然後統計完整塊。時間複雜度:O(\(\sqrt{n}\)).
為了方便,我們都預設下文的分塊大小為\(\sqrt{n}\)。
例題(更新中)
例1:分塊1
分塊模板題,單點修改,區間查詢
既然是模板題,非常好想,只要仔細閱讀本文前面就可以輕鬆AC
注意!!!!開快讀,因為原來是樹狀陣列的題,所以卡常!
附上程式碼
點選檢視程式碼
#include<bits/stdc++.h> using namespace std; typedef long long ll; int n,len; int v[50005],b[50005],tag[50005]; void add(int l,int r,int d) { for(int i=l;i<=min(b[l]*len,r);i++)v[i]+=d; if(b[l]!=b[r]) { for(int i=(b[r]-1)*len+1;i<=r;i++)v[i]+=d; } for(int i=b[l]+1;i<=b[r]-1;i++)tag[i]+=d; } int main() { cin>>n;len=sqrt(n); for(int i=1;i<=n;i++)cin>>v[i]; for(int i=1;i<=n;i++)b[i]=(i-1)/len+1; for(int i=1;i<=n;i++){ int c,l,r,d; cin>>c>>l>>r>>d; if(c==0)add(l,r,d); if(c==1)cout<<v[r]+tag[b[r]]<<'\n'; } return 0; }
例2:分塊2
這道題就是區間修改+區間查詢。
不需要開快讀。
點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,len,b[120005],m;
ll v[120005],tag[120005],sum[120005];
void add(int l,int r,ll d){
for(int i=l;i<=min(b[l]*len,r);i++){
v[i]+=d;sum[b[l]]+=d;
}
if(b[l]!=b[r])
for(int i=(b[r]-1)*len+1;i<=r;i++)
v[i]+=d,sum[b[r]]+=d;
for(int i=b[l]+1;i<=b[r]-1;i++)tag[i]+=d;
}
ll ask(int l,int r){
ll ans=0;
for(int i=l;i<=min(b[l]*len,r);i++)ans+=v[i]+tag[b[l]];
if(b[l]!=b[r])
for(int i=(b[r]-1)*len+1;i<=r;i++)
ans+=v[i]+tag[b[r]];
for(int i=b[l]+1;i<=b[r]-1;i++)ans+=sum[i]+len*tag[i];
return ans;
}
int main(){
n=read();m=read();len=sqrt(n);
for(int i=1;i<=n;++i)v[i]=read();
for(int i=1;i<=n;++i){
b[i]=(i-1)/len+1;sum[b[i]]+=v[i];
}
for(int i=1;i<=m;++i){
int c=read(),l=read(),r=read();
if(c==1){
int d=read();add(l,r,d);
}
if(c==2)printf("%lld\n",ask(l,r));
}
return 0;
}
例3:分塊3
給出一個長\(n\)的陣列,以及n個操作,涉及區間加法,詢問區間內小於某個值x的元素個數。
\(1、\)不完整的塊暴力列舉即可
\(2、\)每個塊內需要有序,所以在每次區間加法之後需要對端點所在的兩個塊重新排序
\(3、\)每次查詢在塊內二分,以及暴力列舉兩個端點所在塊的元素。
時間複雜度證明:
設塊長為\(q\),則總共\(\frac{n}{q}\)個塊
預處理排序,複雜度\(O(n\log{n})\)
對於區間修改操作,仍然是維護加法tag,暴力修改兩邊的塊+重新排序,複雜度\(O(q\log{\frac{n}{q}})\)
對於區間查詢操作,最壞在\(O(\frac{n}{q})\)個塊內二分,以及暴力列舉\(O(q)\)個元素,複雜度\(O(q+\frac{n}{q}\log{q})\)
總複雜度\(O(n\log{n}+nq\log{\frac{n}{q}}+\frac{n^2\log{q}}{q})\)
一般取q=\(\sqrt{n}\)即可,此時複雜度\(O(n\sqrt{n}\log{\sqrt{n}})\)
但是,如果將q取稍小些,複雜度會更優,由於證明較為複雜,不再提及。
程式碼實現:
點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,len,m;
int v[500005],b[500005],tag[500005];
vector<int> g[505];
inline void reset(int x){
g[x].clear();
for(int i=(x-1)*len+1;i<=min(x*len,n);++i)g[x].push_back(v[i]);
sort(g[x].begin(),g[x].end());
}
inline void add(int l,int r,int d) {
for(register int i=l;i<=min(b[l]*len,r);++i)v[i]+=d;
reset(b[l]);
if(b[l]!=b[r]) {
for(register int i=(b[r]-1)*len+1;i<=r;++i)v[i]+=d;
reset(b[r]);
}
for(register int i=b[l]+1;i<=b[r]-1;++i)tag[i]+=d;
}
inline int ask(int l,int r,int d){
int ans=0;
for(register int i=l;i<=min(b[l]*len,r);++i){
if(v[i]+tag[b[l]]<d)++ans;
}
if(b[l]!=b[r]){
for(register int i=(b[r]-1)*len+1;i<=r;++i)
if(v[i]+tag[b[r]]<d)ans++;
}
for(register int i=b[l]+1;i<=b[r]-1;++i){
int x=d-tag[i];
ans+=lower_bound(g[i].begin(),g[i].end(),x)-g[i].begin();
}
return ans;
}
int main() {
ios::sync_with_stdio(0);cin.tie(0);
n=read();len=sqrt(n);
for(register int i=1;i<=n;++i)v[i]=read();
for(register int i=1;i<=n;++i){
b[i]=(i-1)/len+1;
g[b[i]].push_back(v[i]);
}
for(register int i=1;i<=b[n];++i)
sort(g[i].begin(),g[i].end());
for(register int i=1;i<=n;++i){
int c=read(),l=read(),r=read(),d=read();
if(c==0){
add(l,r,d);
}
if(c==1)cout<<ask(l,r,d*d)<<'\n';
}
return 0;
}