動態DP,ddp
動態DP?動態動態規劃?
個人理解:動態DP,就是普通DP加修改操作,然後就變成了個毒瘤題。
直接就著例題寫吧。
例題
P4719 【模板】"動態 DP"&動態樹分治
求樹上最大獨立集。要求支援修改點權。n<=1e5.
演算法原理
首先不帶修的最大獨立集是一個NOIP題:
\(f[cur][0/1]\) 表示 \(cur\) 選/不選 其子樹內(含 \(cur\))的被選點權值和。
\[f[cur][0]= \sum max(f[to][0], f[to][1]) \]
\[f[cur][1] = \sum f[to][0] \]
既然要求支援修改,我們可以拿出對付樹的利器:樹鏈剖分。將樹剖分成重鏈和輕邊。然後 \(f[cur][0/1]\)
\[g[cur][0]= \sum max(g[to][0], g[to][1]) \]
\[g[cur][1] = \sum g[to][0] \]
為了描述方便,規定 \(i + 1\) 為在重鏈上與 \(i\) 相鄰的比 \(i\) 深的那個點。則:
\[f[i][0] = g[i][0] + max(f[i + 1][0], f[i + 1][1]) \]
\[f[i][1] = g[i][1] + f[i + 1][0] \]
然後我們發現這樣老是[0][1]不方便,直接用 \(2×1\) 的矩陣來表示 \(f, g\)
\[\begin{bmatrix} g_{i,0} & g_{i, 0}\\ g_{i, 1} & -\infty\end{bmatrix} × \begin{bmatrix} f_{i+1, 0} \\ f_{i+1, 1} \end{bmatrix}=\begin{bmatrix} f_{i,0} \\ f_{i,1} \end{bmatrix}\]
這樣,我們就只用維護每個點的 \(g\) 矩陣,用到 \(f\) 的時候直接拿 \(g\) 矩陣乘一下即可。這個 \(g\) 矩陣是重鏈上的一堆矩陣,因此我們可以拿線段樹維護。
現在考慮修改帶來的影響
如果我們修改了某一個點的權值,那麼這個點的 \(g\) 矩陣將會改變,不過重鏈上的其它 \(g\) 矩陣不會改變。但是,重量頂端的父親的 \(g\) 矩陣會因為重鏈上的 \(f\) 的改變而改變,因此我們需要修改重鏈父親的 \(g\) 矩陣,以及重鏈父親所在重鏈的頂端的父親的 \(g\) 矩陣...
流程大概是:修改 \(cur\) 的 \(g\) 矩陣,計算 \(top\) 的 \(f\),(如果 \(cur\) 尚不為樹根),cur = fa[top[cur]]
,重複。
程式碼實現
具體實現的時候,我們自然可以嚴格按照流程的做法來。不過,相比樹剖瘋狂跳 \(fa[top]\),LCT的 \(Access\) 函式也可以較為輕鬆地實現這種操作,並且LCT可以將資訊直接維護到點上,避開線段樹無比笨拙的修改查詢操作,砍掉一個 \(log\),而且還不用 \(makeroot\),因此不用 \(pushr,pushdown\),是為數不多的LCT比樹剖好寫的題。所以,這題用LCT維護原圖子樹資訊是個不錯的選擇。
還有一些簡化程式碼的小 trick:
-
一開始可以讓所有邊都為虛邊,直接一遍DFS計算出所有 \(g\) 矩陣。
-
我們保持1為根節點不變;所有矩陣都可以開成 2×2 的矩陣。這是因為我們矩陣初始化全為 -inf,如果真的要拿一個 2×2 的矩陣和 2×1 的矩陣乘,由於 2×1 矩陣的第二列全為 -inf,最終結果的第二列也將是 -inf,並不會影響答案。
-
最終統計答案的時候,可以直接 \(splay(1)\) 然後就拿 1 節點的 \(mul\) 計算答案。本來應該是 1 所在的實鏈的底端的那個 \(f\) 矩陣乘其它的 \(g\) 矩陣,但是我們發現底端 \(g\) 矩陣的第一列恰好是 \(f\) 矩陣的第一列,因此乘出來的第一列就恰好是答案。
關鍵程式碼
略微感受到shadowice大佬考場調bug的感覺了,我還是剛學完ddp,理清思路再寫,還寫出了五個bug。
int h[N];
matrix val[N], mul[N];
int son[N][2], fa[N];
inline void pushup(int cur) {
mul[cur] = val[cur];
int ls = son[cur][0], rs = son[cur][1];
if (ls) mul[cur] = mul[ls] * mul[cur];
if (rs) mul[cur] = mul[cur] * mul[rs];
}
inline void Access(int cur) {
for (register int p = cur, lst = 0; p; lst = p, p = fa[p]) {
splay(p);//Attention!
if (lst) {
val[p].h[0][0] -= max(mul[lst].h[0][0], mul[lst].h[1][0]);//Attention!!! : mul
val[p].h[1][0] -= mul[lst].h[0][0];//Attention!!! : mul
}
int rs = son[p][1];
if (rs) {
val[p].h[0][0] += max(mul[rs].h[0][0], mul[rs].h[1][0]);//Attention!!!
val[p].h[1][0] += mul[rs].h[0][0];//Attention!!!
}
val[p].h[0][1] = val[p].h[0][0];
son[p][1] = lst;
pushup(p);
}
}
int g[N][2];
void dfs(int cur, int faa) {
g[cur][1] = h[cur]; fa[cur] = faa;
for (register int i = head[cur]; i; i = e[i].nxt) {
int to = e[i].to; if (to == faa) continue;
dfs(to, cur);
g[cur][0] += max(g[to][0], g[to][1]);
g[cur][1] += g[to][0];
}
val[cur].h[0][0] = val[cur].h[0][1] = g[cur][0];
val[cur].h[1][0] = g[cur][1];//Attention!
mul[cur] = val[cur];
}
inline void modify(int cur, int v) {
Access(cur), splay(cur);
// val[cur].h[0][0] += v - h[cur];
// val[cur].h[0][1] = val[cur].h[0][0];
val[cur].h[1][0] += v - h[cur];//Attention!!!
h[cur] = v;//Attention!
pushup(cur);
}
int main() {
dfs(1, 0);
while (m--) {
modify(x, v);
splay(1);
ans = max(mul[1].h[0][0], mul[1].h[1][0]);
}
}