淺談分塊
阿新 • • 發佈:2021-01-15
更優體驗請移步[CSDN](https://blog.csdn.net/LZX_lzx/article/details/112639743)
# 前言
$NOIP$已過,訓練難度瞬間變大。很多沒有學過的知識點以各種方式出現在題目裡。而本蒟蒻的腦子裡只有那慘兮兮一點點的演算法,於是本蒟蒻就開始走上惡補知識點的道路。
突然想起來很久很久之前有道用分塊做的題目,當時聽的雲裡霧裡,然後同年級的某位大佬表示:分塊很簡單。今天又聽到同學說起分塊,就上[OI-WIKI](https://oi-wiki.org/)查了一下,沒想到很快就理解然後敲題了……
# 何為分塊
分塊其實是一種**思想**,本質上跟**暴力**差不多,常用於處理區間問題。做法是將區間分成一個個小塊,然後暴力維護,時間複雜度和分出來的塊的個數及塊的大小有關
----
結合例題講解分塊的具體操作
# 例題1
[LOJ#6280.數列分塊入門4](https://loj.ac/p/6280)
給出一個長為$n$的數列,以及$n$個操作
操作有兩種情況,每次操作輸入4個數$opt、l、r、c$
若$opt=0$,表示將位於$[l,r]$的之間的數字都加$c$。
若$opt=1$,表示詢問位於$[l,r]$的所有數字的和$mod\ (c+1)$ 。
$1\le n \le50000$,保證答案在$long\ long$範圍內
# 例題思路分析及程式碼
只要學過線段樹,第一秒想到的基本都會是線段樹,但是這題有一個很大的不同在於每次求值的時候會模一個非固定的數,那麼如果要用線段樹來做就需要在建樹的時候不取模,求值的時候再取模,這樣的話就有可能會導致線段樹上的值超過$long\ long$甚至$unsigned\ long\ long$範圍($\_\_int128$就不要多想了)
所以我們要拋棄線段樹,去尋找另一種解決方案。而分塊,就是解決這個問題的一個很好的辦法
對於一個區間,我們把它分成若干個長度為$s$的塊,最後一個塊的長度可以不足$s$,因為沒有規定$s$必須是$n$的因數
一個區間$a$就可以分成
$\underbrace{a_1,a_2\ldots,a_s}_{b_1},\underbrace{a_{s+1},\ldots,a_{2s}}_{b_2},\dots,\underbrace{a_{(s-1)\times s+1},\dots,a_n}_{b_{\frac{n}{s}}}$
其中$b_i$維護第$i$個塊內的和,可以在讀入的時候就記錄好每個元素是哪個塊,同時維護$b$陣列
## 更改
分類討論一下
如果$l,r$在同一塊內,則直接暴力更改,時間複雜度$O(s)$
如果不在,就可以分三部分:(1)以$l$開頭的一個不完整塊;(2)中間若干個完整塊;(3)以$r$結尾的一個不完整塊。其中(1)(3)部分可以暴力更改,(2)部分就直接更改$b_i$,以及$x_i$。其中$x_i$表示整個區間加上了多少,時間複雜度$O(\dfrac{n}{s}+s )$
## 查詢
跟更改很像,也是要分類討論
$l,r$在同一個塊內就直接暴力統計,同時注意加上$x$陣列,時間複雜度$O(s)$
不在同一個塊內也是分三部分,分法同更改部分,(1)(3)部分查詢也是暴力,跟$l,r$同塊一樣,注意加上$x$陣列。(2)部分直接加上中間完整塊的$b$陣列,這裡就不用加上$x$陣列。時間複雜度$O(\dfrac{n}{s}+s)$
## 時間複雜度分析
綜合更改和查詢,一次操作的時間複雜度就是$O(\dfrac{n}{s}+s)$,顯然當$s$是$\sqrt{n}$的時候是最優的,那麼一次操作的時間複雜度就是$O(\sqrt{n})$,總時間複雜度$O(n\sqrt{n})$
## Code
```cpp
#include
#include
#define ll long long
using namespace std;
int n,s,opt,l,r,x;
ll ans,a[50005],id[50005],b[50005],c[50005];
int read()
{
int res=0,fh=1;char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') fh=-1;ch=getchar();}
while (ch>='0'&&ch<='9') res=res*10+ch-'0',ch=getchar();
return res*fh;
}
int main()
{
n=read();
s=sqrt(n);
for (int i=1;i<=n;++i)
{
a[i]=read();
id[i]=(i-1)/s+1;
b[id[i]]+=a[i];
}
for (int i=1;i<=n;++i)
{
opt=read();l=read();r=read();x=read();
if (!opt)
{
if (id[l]==id[r])
{
for (int j=l;j<=r;++j)
a[j]+=x,b[id[l]]+=x;
}
else
{
for (int j=l;id[j]==id[l];++j)
a[j]+=x,b[id[l]]+=x;
for (int j=id[l]+1;j
#include
#include
#define ll long long
using namespace std;
int n,m,len,l,r,x,opt;
ll mx,a[100005],id[100005],b[100005],c[100005];
int read()
{
int res=0,fh=1;char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') fh=-1;ch=getchar();}
while (ch>='0'&&ch<='9') res=res*10+ch-'0',ch=getchar();
return res*fh;
}
int main()
{
freopen("max10.in","r",stdin);
freopen("max10.txt","w",stdout);
n=read();
len=sqrt(n);
for (int i=1;i<=n;++i)
{
a[i]=read();
id[i]=(i-1)/len+1;
b[id[i]]=max(b[id[i]],a[i]);
}
m=read();
while (m--)
{
opt=read();
if (opt==1)
{
l=read();r=read();x=read();
if (id[l]==id[r])
{
for (int i=l;i<=r;++i)
a[i]+=x;
b[id[l]]=-2147483647;
for (int i=(id[l]-1)*len+1;id[i]==id[l];++i)
b[id[l]]=max(b[id[i]],a[i]+c[id[i]]);
}
else
{
for (int i=l;id[i]==id[l];++i)
a[i]+=x;
for (int i=id[l]+1;i