1. 程式人生 > >「分塊」學習筆記

「分塊」學習筆記

# 「分塊」 ## 演算法思想 當我們對於一個很大陣列 $(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