1. 程式人生 > 遊戲 >Bloober Team曾有機會開發《電鋸驚魂》卻主動放棄

Bloober Team曾有機會開發《電鋸驚魂》卻主動放棄

分塊是一種 思想
它用於存在區間問題,並且結合律不是簡單相加的時候
線段樹和樹狀陣列此時不可用,這個時候,我們就要使用分塊

切入正題

分塊思想是這樣的
設陣列長度是n,共有q塊,把陣列存下來,分成\(O(\frac{n}{q})\)段,每段記憶體一個tag表示區間標記,當區間修改的時候,對於整塊,直接增加標記即可。
很類似線段樹的"標記永久化",思想都是一樣的,不過實現方法有很大差別。
而對於不滿一塊的情況下,直接暴力運算即可。

使用條件

可以快速算出一個操作對單獨的數的影響。
塊與塊
一般情況下,n,m \(\leqslant\) 200000
當操作簡單或者資料水時,可以到500000

時間複雜度分析

當塊長為\(O(\frac{n}{q})\)

的時候,對於查詢,直接O(1)查詢陣列即可,注意加上tag。
對於區間修改,在塊內的修改,顯然最多把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;
}

正在更新中……