「分塊」學習筆記
阿新 • • 發佈:2020-08-15
# 「分塊」
## 演算法思想
當我們對於一個很大陣列 $(1e5)$ 進行區間修改和區間查詢時,我們會想到線段樹的 $nlog_n$ 的優秀效率。
分塊——優雅的暴力!!!
我們將區間分成每個大小為 $S$ 的小塊,這樣我們的複雜度就會從 $n$ 降到 $\frac n S$ 的效率。
## 基本思路
### 初始化
我們先將陣列分成長度為 $S$ 小塊,用原下標除以 $S$ 向上取整,就是他分塊後的小塊標號。
```c++
void Init() { //分塊初始化
S = sqrt(n);
for (register int i = 1; i <= n; i ++) {
bel[i] = (i - 1) / S + 1; //向上取整
size[bel[i]] ++; //統計每個塊的大小,為什麼不直接L[i] - R[i],因為可能最後一個塊的大小不是S
sum[bel[i]] += a[i]; //統計每個塊的權值總和
if (!L[bel[i]]) L[bel[i]] = i; //每個塊的左端點
R[bel[i]] = i; //每個塊的右端點
}
}
```
### 區間修改
若我們要將 $l$ 到 $r$ 的區間內都加上權值 $w$。
#### 若這個區間在一個小塊裡面
直接 $S$ 的效率一個一個修改即可。
#### 若這個區間橫跨了多個小塊
如下圖
![](https://images.cnblogs.com/cnblogs_com/Rubyonly233/1814954/o_2008151216001.png)
我們先用 $S$ 效率將左右兩端無法組成一個塊的單點都加上。
然後對於中間的塊,一個一個塊都打上標記,跟線段樹上的標記差不多,最後區間查詢時加上即可。
```c++
void Modify(int l, int r, int w) { //區間修改
if (bel[l] == bel[r]) { //如果這個區間在同一個塊裡
sum[bel[l]] += (r - l + 1) * w; //塊的值加上
for (register int i = l; i <= r; i ++) { //每個單點的值也加上
a[i] += w;
}
}else {
Modify (l, R[bel[l]], w); //最左邊無法組成一個塊的
Modify (L[bel[r]], r, w); //最右邊無法組成一個塊的
for (register int i = bel[l] + 1; i < bel[r]; i ++) { //把中間的塊加上,並打上花火 &!@*^$&*^*&^@
tag[i] += w;
sum[i] += size[i] * w;
}
}
}
```
### 區間查詢
跟區間修改的思想類似
#### 若這個區間在一個小塊裡面
將這個塊裡標記值加上,在加上單點的值即可。
#### 若這個區間橫跨了多個小塊
將無法組成左右無法組成一個小塊的部分用上面的思路加和。
剩下的一塊一塊直接加上 $sum[i]$ 即可。
```c++
inline int Query(int l, int r) { //區間查詢
int ans = 0;
if (bel[l] == bel[r]) {
ans += (r - l + 1) * tag[bel[l]]; //把之前整塊修改的值加上
for (register int i = l; i <= r; i ++) { // 把之前單點修改後的值加上
ans += a[i];
}
}else {
ans += Query(l, R[bel[l]]); //最左邊無法組成塊的值加上
ans += Query(L[bel[r]], r); //最右邊無法組成塊的值加上
for (register int i = bel[l] + 1; i < bel[r]; i ++) {
ans += sum[i]; //這樣我們的sum陣列就可以直接查了
}
}
return ans;
}
```
## 時間效率
自我感覺比線段樹好寫,而且在某些題上會比線段樹跑得快。
例如這個[板子題](https://www.luogu.com.cn/problem/P3372)
### 分塊
![](https://images.cnblogs.com/cnblogs_com/Rubyonly233/1814954/o_2008151234462.png)
![](https://images.cnblogs.com/cnblogs_com/Rubyonly233/1814954/o_2008151239381.png)
### 線段樹
![](https://images.cnblogs.com/cnblogs_com/Rubyonly233/1814954/o_2008151234493.png)
![](https://images.cnblogs.com/cnblogs_com/Rubyonly233/1814954/o_2008151239412.png)
## 練習題
[打一波廣告](https://www.cnblogs.com/liuchanglc/p/13510238.html)
## 程式碼
```c++
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
inline int read(){
int x = 0, w = 1;
char ch;
for (; ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
return x * w;
}
int n, m, S;
int a[maxn];
int bel[maxn], size[maxn], sum[maxn], L[maxn], R[maxn];
int tag[maxn];
void Init() { //分塊初始化
S = sqrt(n);
for (register int i = 1; i <= n; i ++) {
bel[i] = (i - 1) / S + 1; //向上取整
size[bel[i]] ++; //統計每個塊的大小,為什麼不直接L[i] - R[i],因為可能最後一個塊的大小不是S
sum[bel[i]] += a[i]; //統計每個塊的權值總和
if (!L[bel[i]]) L[bel[i]] = i; //每個塊的左端點
R[bel[i]] = i; //每個塊的右端點
}
}
void Modify(int l, int r, int w) { //區間修改
if (bel[l] == bel[r]) { //如果這個區間在同一個塊裡
sum[bel[l]] += (r - l + 1) * w; //塊的值加上
for (register int i = l; i <= r; i ++) { //每個單點的值也加上
a[i] += w;
}
}else {
Modify (l, R[bel[l]], w); //最左邊無法組成一個塊的
Modify (L[bel[r]], r, w); //最右邊無法組成一個塊的
for (register int i = bel[l] + 1; i < bel[r]; i ++) { //把中間的塊加上,並打上花火 &!@*^$&*^*&^@
tag[i] += w;
sum[i] += size[i] * w;
}
}
}
inline int Query(int l, int r) { //區間查詢
int ans = 0;
if (bel[l] == bel[r]) {
ans += (r - l + 1) * tag[bel[l]]; //把之前整塊修改的值加上
for (register int i = l; i <= r; i ++) { // 把之前單點修改後的值加上
ans += a[i];
}
}else {
ans += Query(l, R[bel[l]]); //最左邊無法組成塊的值加上
ans += Query(L[bel[r]], r); //最右邊無法組成塊的值加上
for (register int i = bel[l] + 1; i < bel[r]; i ++) {
ans += sum[i]; //這樣我們的sum陣列就可以直接查了
}
}
return ans;
}
int main(){
n = read(), m = read();
for (register int i = 1; i <= n; i ++) {
a[i] = read();
}
Init();
while (m --) {
int opt = read(), l = read(), r = read();
if (opt == 1) {
int w = read();
Modify(l, r, w);
}else {
printf("%d\n", Query(l, r));
}
}
return