<style>
.reveal .slides {
font: 0.65em "Fira Mono", monospace;
}
.yellow {
color: yellow;
}
.gray {
color: #c9c9c9;
}
.red {
color: #f01f45;
}
.green {
color: #2cd620;
}
</style>
# 根號算法 II
#### **Gino**
---
## 內容大綱
- ★★★★★ 操作分塊
- ★★★★☆ 綜合練習
---
今天這堂課涵蓋的主題只有操作分塊,但難度較高。
所以其他根號的主題我不打算講,今天前半堂課只 focus 在操作分塊。<!-- .element: class="fragment" data-fragment-index="1" -->
後半堂課會讓大家練一些題目,複習這堂還有上堂課提到的知識點。<!-- .element: class="fragment" data-fragment-index="2" -->
有問題都可以隨時問 :D<!-- .element: class="fragment" data-fragment-index="3" -->
---
另外考量到莫隊算法的性質,比較貼近下堂課的主題「離線演算法」。
因此莫隊算法會擺在下一堂課。<!-- .element: class="fragment" data-fragment-index="1" -->
---
<!-- #################################################### -->
## 操作分塊
----
- 之前學過對序列分塊、對值域分塊
- 但「操作」也可以分塊喔!<!-- .element: class="fragment" data-fragment-index="1" -->
----
### What is 操作分塊
- 「有 $Q$ 筆操作,每筆操作可能為**修改**或**詢問**」<!-- .element: class="fragment" data-fragment-index="1" -->
- 簡單來說就是有很多個操作<!-- .element: class="fragment" data-fragment-index="2" -->
- 把操作分組,一組一組處理<!-- .element: class="fragment" data-fragment-index="3" -->
- 這樣很神奇的可以降低複雜度<!-- .element: class="fragment" data-fragment-index="4" -->
----
### [區間修改、單點詢問](https://codeforces.com/edu/course/2/lesson/4/3/practice/contest/274545/problem/E)
有一個長度為 $N$ 的序列 $\langle a_N \rangle$,你要處理 $Q$ 筆操作:
- ADD l r x:將區間 $[a_l, a_r]$ 加上 $x$
- QRY i:詢問 $a_i$ 為多少
$N, Q \leq 10^5$
----
- 把操作每 $\sqrt{Q}$ 個分成一組
- 先處理第一組操作<!-- .element: class="fragment" data-fragment-index="1" -->
- 按照時間依序處理操作<!-- .element: class="fragment" data-fragment-index="2" -->
![](https://i.imgur.com/9HRvbHd.png)
----
- 碰到**修改**時,先不要做任何事
![](https://i.imgur.com/bRjkFhE.png)<!-- .element: class="fragment" data-fragment-index="1" -->
----
- 碰到**修改**時,先不要做任何事
![](https://i.imgur.com/8uuJqFS.png)
###### 紅色標記表示這個**修改**未完成
----
- 碰到**詢問**時,暴力跑過之前的所有修改,看有沒有要加值
- 最多只會跑過 $O(\sqrt{Q})$ 個修改,所以暴力複雜度是好的<!-- .element: class="fragment" data-fragment-index="2" -->
![](https://i.imgur.com/GdR09RF.png)<!-- .element: class="fragment" data-fragment-index="1" -->
----
- 重複之前的動作,直到處理完第一組的操作
![](https://i.imgur.com/lUQ236G.png)
----
- 接下來,我們要把這組裡面**所有的修改**一次做完
- 「做完」的定義是把要加的值加到原本的陣列上<!-- .element: class="fragment" data-fragment-index="1" -->
- 如何**一次做完**?<!-- .element: class="fragment" data-fragment-index="2" -->
### 掃描線!<!-- .element: class="fragment" data-fragment-index="3" -->
----
### 這是基礎掃描線例題
- 把所有修改左右界拆開來,之後全部丟到一個 vector
- 排序<!-- .element: class="fragment" data-fragment-index="2" -->
- $O(N)$ 掃過整個陣列並**修改**。<!-- .element: class="fragment" data-fragment-index="3" -->
![](https://i.imgur.com/eeE9VjF.png)
----
- 第一組的所有操作處理完畢
![](https://i.imgur.com/FXSjyGN.png)
----
- 接下來處理第二組
- 一樣依序處理每個操作<!-- .element: class="fragment" data-fragment-index="1" -->
![](https://i.imgur.com/TgWwLEI.png)
----
- 碰到**修改**時,一樣不要做任何事
![](https://i.imgur.com/16UaReO.png)
----
- 碰到**詢問**時,暴力跑過之前<span class="red">**還沒做的**</span>修改,看有沒有要加值
- 前面組別的修改已經做過了,不用理會<!-- .element: class="fragment" data-fragment-index="1" -->
- 只要跑過同一組的修改就好!<!-- .element: class="fragment" data-fragment-index="2" -->
- $O(\sqrt{Q})$<!-- .element: class="fragment" data-fragment-index="3" -->
![](https://i.imgur.com/b5qtc6y.png)
----
- 重複之前的動作,直到處理完第二組的操作
- 接下來一樣把第二組的所有修改抓出來,做一次掃描線更新陣列<!-- .element: class="fragment" data-fragment-index="1" -->
![](https://i.imgur.com/apkkBD6.png)
----
### 時間複雜度
- 操作每 $O(\sqrt{Q})$ 一塊,總共有 $O(\sqrt{Q})$ 塊。<!-- .element: class="fragment" data-fragment-index="1" -->
- <span class="yellow">處理同一塊的操作時:</span><!-- .element: class="fragment" data-fragment-index="2" -->
- 修改:我們並沒有做任何事,除了最後的掃描線,因此總複雜度 $O(N)$。<!-- .element: class="fragment" data-fragment-index="3" -->
- 詢問:一次詢問 $O(\sqrt{Q})$,總共 $O(\sqrt{Q})$ 個詢問,因此總複雜度 $O(Q)$。<!-- .element: class="fragment" data-fragment-index="4" -->
- <span class="yellow">總共有 $O(\sqrt{Q})$ 塊,故總時間複雜度為 $O((N+Q)\sqrt{Q})$。</span><!-- .element: class="fragment" data-fragment-index="5" -->
----
### 操作分塊的框架
- 把操作分組(通常是每 $\sqrt{Q}$ 個一組)<!-- .element: class="fragment" data-fragment-index="1" -->
- 處理每塊的操作:<!-- .element: class="fragment" data-fragment-index="2" -->
- 對於修改,不要做任何事<!-- .element: class="fragment" data-fragment-index="3" -->
- 對於詢問,暴力跑過同塊的修改,不要跑過更之前的修改(那些修改都已經完成了)<!-- .element: class="fragment" data-fragment-index="4" -->
- 最後把同一塊的修改一起做掉,移動到下一塊,重複一樣的事情<!-- .element: class="fragment" data-fragment-index="5" -->
----
### 休息 10 分鐘/提問
----
### [Xenia and Tree](https://codeforces.com/problemset/problem/342/E)
給一個 $N$ 個節點的樹,一開始所有點都是白色的,有 $Q$ 筆操作。
1. 把一個點塗成黑色。
2. 詢問一個點到某個黑點的最短距離。
![](https://i.imgur.com/rm10F3V.png)
$N, Q \leq 10^5$
----
- 一樣把操作分成一塊一塊
- 處理詢問時,我們把黑點分成「在這塊操作才被塗」還有「在這塊之前就已經塗過」<!-- .element: class="fragment" data-fragment-index="1" -->
- 對於前者,我們暴力跑過這些黑點(反正數量不多)並計算黑點和詢問點的距離<!-- .element: class="fragment" data-fragment-index="2" -->
- 樹上兩點距離 $\Rightarrow$ LCA<!-- .element: class="fragment" data-fragment-index="3" -->
----
- 對於後者,假設我們有一個表格可以查答案
- 令 dis[u] 為 u 到之前那些黑點的最近距離<!-- .element: class="fragment" data-fragment-index="1" -->
- 要維護好 dis,就必須在每塊做完的時候做一次<span class="yellow">**全點對 BFS**</span><!-- .element: class="fragment" data-fragment-index="2" -->
----
### Xenia and Tree 演算法步驟
- 把操作分組<!-- .element: class="fragment" data-fragment-index="1" -->
- 處理每塊的操作:<!-- .element: class="fragment" data-fragment-index="2" -->
- 對於修改,不要做任何事<!-- .element: class="fragment" data-fragment-index="3" -->
- 對於詢問,暴力跑過同塊的修改,計算「在這塊操作才被塗」的黑點到詢問點的距離並取最小值 $\Rightarrow$ LCA。<!-- .element: class="fragment" data-fragment-index="4" -->
- 而對於「在這塊之前就已經塗過」的黑點,我們可以用 dis 查答案。<!-- .element: class="fragment" data-fragment-index="5" -->
- 最後把同一塊的修改一起做掉,也就是做一次**全點對 BFS**,接著移動到下一塊。<!-- .element: class="fragment" data-fragment-index="6" -->
----
### 時間複雜度
- 這裡的 LCA 推薦使用 Euler Tour + Sparse Table 預處理
- 這樣可以 $O(1)$ 回答兩點距離<!-- .element: class="fragment" data-fragment-index="1" -->
- 不過倍增 LCA 也可以過<!-- .element: class="fragment" data-fragment-index="2" -->
- 忘記的回去翻圖論 I(倍增 LCA)或圖論 V(Euler Tour)的簡報<!-- .element: class="fragment" data-fragment-index="3" -->
----
### 時間複雜度(Euler Tour LCA)
- 預處理 LCA 要用的 Sparse Table $O(N \log N)$。<!-- .element: class="fragment" data-fragment-index="1" -->
- 操作每 $O(B)$ 一塊,總共有 $O(\frac{Q}{B})$ 塊。<!-- .element: class="fragment" data-fragment-index="2" -->
- <span class="yellow">處理同一塊的操作時:</span><!-- .element: class="fragment" data-fragment-index="3" -->
- 修改:我們並沒有做任何事,除了最後全點對 BFS $\Rightarrow$ $O(N)$。<!-- .element: class="fragment" data-fragment-index="4" -->
- 詢問:一次詢問 $O(B)$,總共 $O(B)$ 個詢問 $\Rightarrow$ $O(B^2)$。<!-- .element: class="fragment" data-fragment-index="5" -->
- 總共 $O(\frac{Q}{B})$ 塊,故總時間複雜度為 $O(N \log N + \frac{Q}{B}(N+B^2))$。<!-- .element: class="fragment" data-fragment-index="6" -->
- $O(N \log N + \frac{NQ}{B} + QB)$。<!-- .element: class="fragment" data-fragment-index="7" -->
- <span class="yellow">取 $B = \sqrt{N}$,可得最佳複雜度 $O(N \log N + Q \sqrt{N})$。</span><!-- .element: class="fragment" data-fragment-index="8" -->
----
### 時間複雜度(倍增 LCA)
- 預處理倍增表 $O(N \log N)$。
- 操作每 $O(B)$ 一塊,總共有 $O(\frac{Q}{B})$ 塊。
- <span class="yellow">處理同一塊的操作時:</span>
- 修改:我們並沒有做任何事,除了最後全點對 BFS $\Rightarrow$ $O(N)$。
- 詢問:一次詢問 $O(B \log N)$,總共 $O(B)$ 個詢問 $\Rightarrow$ $O(B^2 \log N)$。
- 總共 $O(\frac{Q}{B})$ 塊,故總時間複雜度為 $O(N \log N + \frac{Q}{B}(N+B^2 \log N))$。
- $O(N \log N + \frac{NQ}{B} + QB \log N)$。
- <span class="yellow">取 $B = \sqrt{\frac{N}{\log N}}$,可得最佳複雜度 $O(N \log N + Q \sqrt{N \log N})$。</span>
----
Code:https://codeforces.com/contest/342/submission/159640127
----
## 休息 10 分鐘/提問
----
### [TIOJ 1171 — 我要成為海賊王](https://tioj.ck.tp.edu.tw/problems/1171)
給一個 $N$ 個節點,邊帶權的樹,一開始所有點都是白色的,有 $Q$ 筆操作。
1. 把一個點塗成黑色。
2. 詢問一個點到所有黑點的總距離。
![](https://i.imgur.com/snmuy7s.png)
$N \leq 10^5, \ Q \leq 10^6$
----
跟 Xenia and Tree 很像,只是題目改成:
1. 邊有權重<!-- .element: class="fragment" data-fragment-index="1" -->
2. 最短距離改成總距離<!-- .element: class="fragment" data-fragment-index="2" -->
另外,這題是重剖或樹剖的練習題,$Q$ 到 $10^6$ 是要卡掉分塊,但還是可以拿 51 分。<!-- .element: class="fragment" data-fragment-index="3" -->
----
- 一樣把操作分成一塊一塊
- 處理詢問時,我們把黑點分成「在這塊操作才被塗」還有「在這塊之前就已經塗過」<!-- .element: class="fragment" data-fragment-index="1" -->
- 對於前者,我們暴力跑過這些黑點(反正數量不多)並計算黑點和詢問點的距離<!-- .element: class="fragment" data-fragment-index="2" -->
- 樹上兩點距離 $\Rightarrow$ LCA<!-- .element: class="fragment" data-fragment-index="3" -->
----
- 對於後者,假設我們有一個表格可以查答案
- 令 dp[u] 為 u 到之前那些黑點的總距離<!-- .element: class="fragment" data-fragment-index="1" -->
- 要維護好 dp,就必須在每塊做完的時候做一次<!-- .element: class="fragment" data-fragment-index="2" --><span class="yellow">**全方位木 DP**</span><!-- .element: class="fragment" data-fragment-index="2" -->
----
### TIOJ 1171 演算法步驟
- 把操作分組<!-- .element: class="fragment" data-fragment-index="1" -->
- 處理每塊的操作:<!-- .element: class="fragment" data-fragment-index="2" -->
- 對於修改,不要做任何事<!-- .element: class="fragment" data-fragment-index="3" -->
- 對於詢問,暴力跑過同塊的修改,計算「在這塊操作才被塗」的黑點到詢問點的距離並取最小值 $\Rightarrow$ LCA<!-- .element: class="fragment" data-fragment-index="4" -->
- 而對於「在這塊之前就已經塗過」的黑點,我們可以用 dp 表查答案。<!-- .element: class="fragment" data-fragment-index="5" -->
- 最後把同一塊的修改一起做掉,也就是做一次**全方位木 DP**,接著移動到下一塊。<!-- .element: class="fragment" data-fragment-index="6" -->
----
### 時間複雜度
跟剛剛的 Xenia and Tree 一樣,複雜度為 $O(N \log N + Q \sqrt{N})$ 或是 $O(N \log N + Q \sqrt{N \log N})$。
這裡就不再贅述。
----
### 關於常數
這題時限很緊,加了一些常數優化才拿到 51 分:
1. 預先紀錄好全方位木 DP 的轉移順序,這樣可以避免遞迴的常數。
2. 塊的大小為動態調整。
----
### 關於常數
有些人可能知道「鍊式前向星」這個常數優化方法,但對這題來說優化不大。
有興趣的可以自行搜尋、爬文,這裡不贅述。
----
Code
```cpp=
const int maxn = 1e5 + 5;
const int maxq = 1e6 + 5;
vector<pair<int, ll> > g[maxn];
int n, q;
bitset<maxn> col;
ll dep[maxn];
int in[maxn], stp = 1;
int lg[maxn*2];
ll tour[maxn*2];
ll spt[18][maxn*2];
vector<int> leaf;
int tr1[maxn], tr2[maxn], tr1ptr, tr2ptr;
ll dp[maxn];
int sub[maxn];
int mod[maxn], mid[maxn];
int qry[maxq], qid[maxq];
void dfs(int u, ll curdep) {
dep[u] = curdep;
in[u] = stp;
tour[stp++] = curdep;
if (!g[u].empty()) tr2[tr2ptr++] = u;
Each(e, g[u]) {
int v = e.F; ll w = e.S;
dfs(v, curdep+w);
tour[stp++] = curdep;
}
if (g[u].empty()) leaf.emplace_back(u);
else tr1[tr1ptr++] = u;
}
void init() {
cin >> n >> q;
col.reset();
REP(i, n-1) {
int u, v; ll w;
cin >> u >> v >> w; u++, v++;
g[u].emplace_back(mp(v, w));
}
for (int cnt = 0; cnt < 18; cnt++) {
for (int i = (1<<cnt); i < (1<<(cnt+1)); i++) {
if (i >= maxn*2) break;
lg[i] = cnt;
}
}
dfs(1, 0);
for (int i = 1; i < stp; i++) {
spt[0][i] = tour[i];
}
for (int k = 1; k < 18; k++) {
for (int i = 1; i+(1<<k)-1 < stp; i++) {
spt[k][i] = min(spt[k-1][i], spt[k-1][i+(1<<(k-1))]);
}
}
}
void buildDis() {
// do rerooting DP
Each(u, leaf) sub[u] = col[u], dp[u] = 0;
int u, v; ll w;
REP(i, tr1ptr) {
u = tr1[i];
sub[u] = col[u];
dp[u] = 0;
Each(e, g[u]) {
v = e.F, w = e.S;
sub[u] += sub[v];
dp[u] += dp[v] + w * sub[v];
}
}
REP(i, tr2ptr) {
u = tr2[i];
Each(e, g[u]) {
v = e.F, w = e.S;
dp[v] += dp[u] - (dp[v] + w*sub[v]) + w * (sub[1]-sub[v]);
}
}
}
void solve() {
bool needBuild = false;
int qcnt = 0, modptr, qryptr;
int op, u, v, uid, l, r, len, k;
ll ans;
while (qcnt < q) {
if (needBuild) buildDis();
modptr = 0;
qryptr = 0;
ll ctrl = 0;
while (ctrl <= 97000) {
if (qcnt >= q) break;
cin >> op >> u; u++;
qcnt++;
if (op == 1) {
if (!col[u]) {
mod[modptr] = u;
mid[modptr] = qcnt;
modptr++;
col[u] = true;
}
} else if (op == 2) {
qry[qryptr] = u;
qid[qryptr] = qcnt;
qryptr++;
ctrl += modptr;
}
}
REP(i, qryptr) {
u = qry[i], uid = qid[i];
ans = dp[u];
REP(j, modptr) {
v = mod[j];
if (mid[j] > uid) break;
l = in[u], r = in[v];
if (l > r) swap(l, r);
len = r-l+1;
k = lg[len];
ans += dep[u] + dep[v] - 2 * min(spt[k][l], spt[k][r-(1<<k)+1]);
}
cout << ans << endl;
}
needBuild = modptr > 0;
}
}
```
---
## 綜合練習
----
### [Lena and Queries](https://codeforces.com/contest/678/problem/F)
有一個集合,一開始為空,接下來有 $Q$ 筆操作:
1. 加入一個線型函數 $f(x) = ax + b$ 到集合裡。
2. 拔掉一個線型函數。
3. 給定 $x$,詢問 $f(x)$ 的最大值。
$1 \leq Q \leq 3 \times 10^5$
----
### [NEOJ 741 橋梁](https://neoj.sprout.tw/problem/741/)
給一張 $N$ 點 $M$ 邊的帶權圖,接下來有 $Q$ 筆操作:
1. 修改一條邊的權重
2. 給定 s, w,求從 s 出發,在「只能經過邊權不超過 w 的邊」的情況下,能走到多少個點?
$N \leq 5 \times 10^4, \ M, Q \leq 10^5$
----
### [Serega and Fun](https://codeforces.com/contest/455/problem/D)
### [Strange Queries](https://codeforces.com/gym/101138/problem/D)
### [NEOJ 小咲的玩具](https://neoj.sprout.tw/problem/722/)
{"metaMigratedAt":"2023-06-17T02:20:34.926Z","metaMigratedFrom":"YAML","title":"根號算法 II","breaks":true,"slideOptions":"{\"transition\":\"fade\"}","description":"★★★★★ 操作分塊","contributors":"[{\"id\":\"ac1507e0-f05c-4708-bdd2-c56d13fb0dbb\",\"add\":14170,\"del\":754}]"}