---
tags: IOICamp
---
# IOICamp Day1 下午 - 動態規劃 & 圖論
講師:waynetuinfor
講義 (動態規劃):https://www.csie.ntu.edu.tw/~b07902024/dp-lecture.pdf
講義 (圖論):
[TOC]
## dp
- 重複子問題 (overlapping subproblem)
- 最優子結構 (optimal substructure)
- 狀態 (state)
- 轉移 (transition)
- $O(N^x) \times O(N^y) = O(N^{x+y}) \rightarrow xD/yD$
- 適用最佳化問題、計數問題
**例1. LIS**
```cpp=
int f(int i) {
int res = i;
for (int j = 1; j < i; ++j) {
if (A[j] < A[i]) res = max(res, f(j) + 1);
}
}
```
$\mathcal{O}(N \times 2^N)$
```cpp=
int f(int i) {
if (dp[i] > 0) return dp[i];
int res = i;
for (int j = 1; j < i; ++j) {
if (A[j] < A[i]) res = max(res, f(j) + 1);
}
return dp[i] = res;
}
```
$\mathcal{O}(N^2)$
### 實用技巧
- 增加轉移的維度
**例2. Pokemon Go**
$dp(i,j) = \max(dp(i-1,j), dp(i-1,j-1) + exp(i-1,j-1) \times A_i) \rightarrow 錯誤$
$dp(i,j,k) = \max(dp(i-1,j,k), dp(i-1,j-1,k-B_i) + (k-B_i) \times A_i) \rightarrow 正確$
- 減少冗餘的狀態
**例3. Two Path**
$dp(x_1,y_1,x_2,y_2) = value(x_1,y_1,x_2,y_2) + \max_{x_1',y_1',x_2',y_2'}dp(x_1',y_1',x_2',y_2') \rightarrow \mathcal{O}(N^4)$
> 離原點距離相同,以距離跟 $x~or~y$ 可以推算出另一維的位置 $\rightarrow \mathcal{O}(N^3)$。
$dp(x_1,x_2,k) = value() + \max_{x_1',x_2'}dp(x_1',x_2',k-1)$
- 以資料結構維護狀態
- 滾動陣列
**例4.**
在 $dp(i, *)$ 只跟 $dp(i-1, *)$ 有關係的時候,可以利用 $i$ 跟 $i-1$ 奇偶性不同來滾動
- 滾動陣列輸出解
路徑被滾動掉了 $\rightarrow$ 改成只需求出每個點。
分治!
複雜度多帶一個 $\log$
- 狀態壓縮
**例5. DAG上最短路**
每個點有 $7$ 種顏色
`dp[kN][2][2][2][2][2][2][2]` 顯然爛爆
`dp[kN][1 << 7]` 方便很多
- 快速莫比烏斯變換 $\text{FMT (SOS dp)}$ (?)
參考資料:https://codeforces.com/blog/entry/45223
**例6. Sum Of Subset**
$\displaystyle\hat f(S) = \sum_{S' \subseteq S}f(S')$
```cpp=
for (int s = 0; s < (1 << N); ++s) {
dp[s] = f[0];
int subset = s;
while (subset > 0) {
dp[s] += f[subset];
subset = (subset - 1) & s;
}
}
```
$\mathcal{O}(3^N)$
$L(S,i) = S$ 的子集中跟 $S$ 前 $i$ 個一樣的人
$dp(S,i) = \begin{array}{lcr}
dp(S,i+1) & \mbox{if} ~ i \notin S\\
dp(S,i+1)+dp(S \backslash \{i\},i+1) & \mbox{if} ~ i\in S
\end{array}$
### 單調隊列優化 (?)
**例7. Sliding Windows**
```cpp=
deque<int> dq;
for (int i = K, j = i; i <= N; ++i) {
while (j <= i) {
while (!dq.empty() and A[j] < A[dq.back()]) dp.pop_back();
dq.push_back(j++);
}
while (!dq.empty() and i - dq[0] >= K) dp.pop_front();
ans[i] = A[dq[0]];
}
```
均攤分析:$\mathcal{O}(N)$
**[例8. Watching Fireworks is Fun (CF 372 C)](https://codeforces.com/problemset/problem/372/C)**
**例9. 多重背包問題**
$dp(i,j) = \max{dp(i-1, j-x)}$
$\displaystyle dp(i,j) = \lfloor\frac{j}{w_i}\rfloor \times v_i + \max_{0 \le \frac{j-k}{w_i}\le s_i, j \equiv k \pmod {w_i}}\{dp(i-1,k) - \lfloor\frac{k}{w_i}\rfloor \times v_i\}$
### 斜率優化 (?)
參考資料:https://codeforces.com/blog/entry/8219
參考資料:https://tioj.ck.tp.edu.tw/uploads/attachment/5/27/5.pdf
$1D/1D$ 優化
$\displaystyle dp(i) = c_i + \max_{j \le R_i}\{a_jx_i + b_j\}$
找最大的 $a_jx_i + b_j \rightarrow$ 當成 $y = a_jx_i + b_j$ 的直線
$\rightarrow$ 找凸包 (Convex Hull Optimization)
均攤分析:$\mathcal{O}(N \lg N)$
**[例10. Commando (APIO 2010)](http://www.codah.club/tasks.php?show_task=5000001044)**
$\displaystyle \begin{array}{rtl}
dp(i) & = \max_{j<i}\{dp(j)+a(S_i-S_j)^2 + b(S_i-S_j) + c\}\\
& = \max_{j<i}\{dp(j) + aS_i^2 + aS_j^2 - 2aS_iS_j + bS_i - bS_j + c\}\\
& = \max_{j<i}\{-2aS_jS_i + dp(j) + aS_i^2 + aS_j^2 - bS_j\} + aS_i^2 + bS_i + c\\
\end{array}$
- 單調過期
### 分治優化 (?)
**例11. 二維函數各點極值**
**[例12. Yakiniku Restaurants (AtCoder arc067 F)](https://atcoder.jp/contests/arc067/tasks/arc067_d)**
證明凸四邊形不等式 $F(l_1,r_1) + F(l_2,r_2) \ge F(l_1,r_2) + F(l_2,r_1)$
```
Solve(L, R, L', R') {
if (L > R) return;
M = (L + R) / 2;
M' = L';
for (i = L'; i <= min(R', M); ++i) {
if (F(i, M) > F(M', M)) M' = i;
}
Solve(L, M - 1, L', M');
Solve(M + 1, R, M', R);
}
```
$\mathcal{O}(nm \lg n)$
### Aliens 優化 (WQS 二分搜) (?)
參考資料:https://codeforces.com/blog/entry/49691
參考資料:https://tioj.ck.tp.edu.tw/uploads/attachment/5/51/10.pdf
**例13. Utilitarianism**
**例14. 凹函數求值**
$M(p)$ 為 $f(x) - px$ 的最大值
$V(p)$ 為最大值發生的位置
- 內插法
$f(K) = f(V(p)) + \frac{f(V(p+1)) - f(V(p))}{V(p+1) - V(p)}(K - V(p))$
- 直接求
**[例15. Aliens (IOI 2016)](https://tioj.ck.tp.edu.tw/problems/1961)**
## Graph
定義 (on DFS Tree):
- 樹邊
- 回邊
- 前向邊
- 交錯邊
定義:
- 點 $k$ 連通:移除任意 $k-1$ 個點圖仍連通
- 邊 $k$ 連通:移除任意 $k-1$ 條邊圖仍連通
判斷邊雙聯通 $\rightarrow$ 找橋
判斷點雙聯通 $\rightarrow$ 找割點
### Tarjan (?)
- Robbin's Theorem
```cpp=
void TarjanDFS(int v, int p) {
stk.push(v);
vis[v] = true;
low[v] = dep[v] = ~p ? depth[p] + 1 : 0;
for (int u : g[v]) {
if (u == p) continue;
if (vis[u]) {
low[v] = min(low[v], dep[u]);
}
else {
TarjanDFS(u, v);
low[v] = min(low[v], low[u]);
}
}
if (low[v] == dep[v]) {
// the edge )v, p_ is a bridge if v is not the root vertex
while (!stk.empty()) {
int x = stk.top();
stk.pop();
bcc[x] = nbcc;
if (x == v) break;
}
++nbcc;
}
}
```
### 縮點
把連通分量縮成一點,只剩下橋 $\rightarrow$ 圖變成一棵樹。
邊雙連通分量:樹
點雙連通分量:圓方樹
### 最近公共祖先 LCA
1. 跳到一樣的深度
2. 一層一層跳上去
每次查詢需耗時 $\mathcal{O}(N)$,考慮以倍增法加速:存下所有節點 $2^k$ 的祖先,便可以以 $\mathcal{O}(\lg N)$ 時間完成步驟 (1), (2)。
> $p(v, 2^k) = p(p(v, 2^{k-1}), 2^{k-1})$
```cpp=
int LCA(int u, int v) {
if (dep[u] > dep[v]) swap(u, v);
for (int i = 0; i < kLog; ++i) {
// if the i-th bit of (dep[v] - dep[u]) is 1
if (dep[v] - dep[u] >> i & 1) v = fa[v][i];
}
if (u == v) return u; // special case
for (int i = kLog-1; i >= 0; --i) {
if (fa[u][i] != fa[v][i]) {
u = fa[u][i];
v = fa[v][i];
}
}
// one step away from the LCA
return fa[u][0];
}
```
### 樹壓平
紀錄 DFS 時進入離開時間,同一子樹內編號必定連續,可以做子樹修改、子樹和。
### 樹上尤拉回路 (?)
可以用 RMQ 加速至 $\mathcal{O}(\lg N)$ 預處理,$\mathcal{O}(1)$ 查詢。
### 樹鍊剖分
樹壓平的時候,同一子樹內編號必定連續,但路徑不會連續。
這時,需要選取一個恰當的順序 DFS,即可以保證同一條樹鍊在壓平後的編號連續,即可以以資料結構維護。
輕重鍊剖分:優先 DFS 子樹較大的子樹,保證任意點跳到根只會經過 $\le \lg N$ 條樹鍊。若區間詢問複雜度 $\mathcal{O}(\lg N)$,則整體複雜度 $\mathcal{O}(\lg^2N)$。
### 重心剖分 (?)
定義:一個節點 $v$ 被稱為重心 (centroid) 若移除 $v$ 之後最大連通塊的大小 $\le \lfloor \frac{N}{2} \rfloor$。
重心分治:計算所有會經過重心的答案,移除重心後,進入各子樹遞迴。
$\displaystyle T(N) = \sum{T(s_i)} + f(N)$
重心樹:構造出一棵 相關的(?) 樹使深度 $\le \lg N$,會破壞部分原本樹上的邊。
構造法:選擇重心,遞迴找到子樹的重心並連邊。構造時間 $\mathcal{O}(N \lg N)$。
如何尋找重心?
### 樹 DP (?)
### 全方位木 DP (?)
<!-- <style>
.mathjax .mjx-chtml {
overflow-x: auto;
overflow-y: hidden;
max-width: 100%;
}
</style> -->