1. 程式人生 > 實用技巧 >動態DP,ddp

動態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/1]\) 表示不考慮重兒子\(f\)。那麼有:

\[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\)

,這樣的話,我們就發現 \(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:

  1. 一開始可以讓所有邊都為虛邊,直接一遍DFS計算出所有 \(g\) 矩陣。

  2. 我們保持1為根節點不變;所有矩陣都可以開成 2×2 的矩陣。這是因為我們矩陣初始化全為 -inf,如果真的要拿一個 2×2 的矩陣和 2×1 的矩陣乘,由於 2×1 矩陣的第二列全為 -inf,最終結果的第二列也將是 -inf,並不會影響答案。

  3. 最終統計答案的時候,可以直接 \(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]);
	}
}