\(DDP\)是指一類須要支持修改的\(DP\)問題,常見的主要是帶修樹形\(DP\),能夠用樹鏈剖分結合矩陣乘法優化修改的複雜度ios
從例題來分析:洛谷P4719函數
題目大意:給出\(n\)個點的樹,每一個點有點權,共\(m\)次操做,每次修改一個點的點權,求每次修改後樹的最大權獨立集的權值大小優化
\(n, m \le 1e5\)spa
若是不帶修改,容易想到設\(f_{u, 0/1}\)來表示以\(u\)爲根的子樹,選/不選\(u\)的答案,推出轉移:
\[ \begin{align} f_{u, 0} & = \sum_{v \in son_u} \max(f_{v, 0}, f_{v, 1}) \\ f_{u, 1} & = w_u + \sum_{v \in son_u} f_{v, 0} \end{align} \]
答案就是\(\max (f_{1, 0}, f_{1, 1})\)設計
可是這樣單次修改須要修改到根的路徑上的全部點,是\(O(n)\)的code
既然修改的是一條路徑,不妨試試樹鏈剖分get
可是按上面的方式轉移顯然不能將一條鏈上的轉移合併,因此考慮從新設計一下狀態,\(f\)的含義不變,增長一個\(g_{u, 0/1}\)表示只考慮\(u\)的輕子樹的答案,那麼就有:
\[ \begin{align} g_{u, 0} & = \sum_{v \in lson_u} \max(f_{v, 0}, f_{v, 1}) \\ g_{u, 1} & = w_u + \sum_{v \in lson_u} f_{v, 0} \\ f_{u, 0} & = g_{u, 0} + \max(f_{hv[u], 0}, f_{hv[u], 1}) \\ f_{u, 1} & = g_{u, 1} + f_{hv[u], 0} \end{align} \]string
觀察後兩式中\(f\)和\(g\)的關係咱們發現貌似能夠寫成形如矩陣乘法的形式,若是咱們從新定義矩陣乘法爲\(C_{i, j} = \max_{k = 1}^{n} (A_{i, k} + B_{k, j})\),就會有:
\[ \left[ \begin{matrix} f_{hv[u], 0} & f_{hv[u], 1} \end{matrix} \right] * \left[ \begin{matrix} g_{u, 0} & g_{u, 1} \\ g_{u, 0} & -\infty \end{matrix} \right] = \left[ \begin{matrix} f_{u, 0} & f_{u, 1} \end{matrix} \right] \]
容易證實新定義的矩陣乘法具備結合律,那麼就能夠樹鏈剖分後用線段樹維護重鏈上第二個矩陣的積it
因而咱們能夠發現:io
因此咱們只須要線段樹維護\(g\)的積,同時維護下每條鏈鏈頂的\(f\)就好了
具體作法就是先在線段樹中更新當前節點的\(g\),而後更新當前鏈頂的\(f\),而後跳到鏈頂的父親,重複這個過程就能夠了,更具體的可見代碼的\(modify\)函數
複雜度\(O(n \log^2 n)\)
一個細節:矩陣乘法不具備交換律,注意是從下往上乘,按\(dfs\)序從大到小乘
PS.此題還有\(O(n \log n)\)的\(LCT\)作法
#include <cstdio> #include <cstring> #include <iostream> #include <vector> #define MAXN 100005 typedef long long LL; const LL INF = 0x3f3f3f3f3f3f3f3f; struct Matrix { LL data[2][2]; Matrix() { memset(data, 0, sizeof data); } Matrix(LL a00, LL a01, LL a10, LL a11) { data[0][0] = a00, data[0][1] = a01, data[1][0] = a10, data[1][1] = a11; } static Matrix indentity() { return Matrix(1, 0, 0, 1); } Matrix operator *(const Matrix &) const; }; struct SegmentTree { Matrix data[MAXN << 2]; void modify(int, int, int, int, const Matrix &); void query(int, int, int, int, int, Matrix &); }; char gc(); int read(); void dfs1(int); void dfs2(int); void modify(int, int); int N, M, val[MAXN]; int idx, top[MAXN], bot[MAXN], dep[MAXN], fa[MAXN], dfn[MAXN], size[MAXN], heavy[MAXN]; LL f[MAXN][2], g[MAXN][2]; std::vector<int> trans[MAXN]; SegmentTree sgt; int main() { //freopen("tmp.in", "r", stdin); //freopen("tmp.out", "w", stdout); N = read(), M = read(); for (int i = 1; i <= N; ++i) val[i] = read(); for (int i = 1; i < N; ++i) { int u = read(), v = read(); trans[u].push_back(v); trans[v].push_back(u); } dfs1(1); top[1] = 1; dfs2(1); while (M--) { int x = read(), y = read(); modify(x, y); printf("%lld\n", std::max(f[1][0], f[1][1])); } return 0; } inline char gc() { static char buf[1000000], *p1, *p2; if (p1 == p2) p1 = (p2 = buf) + fread(buf, 1, 1000000, stdin); return p1 == p2 ? EOF : *p2++; } inline int read() { int res = 0, op; char ch = gc(); while (ch != '-' && (ch < '0' || ch > '9')) ch = gc(); op = (ch == '-' ? ch = gc(), -1 : 1); while (ch >= '0' && ch <= '9') res = (res << 1) + (res << 3) + ch - '0', ch = gc(); return res * op; } void dfs1(int u) { dep[u] = dep[fa[u]] + 1; size[u] = 1; for (int i = 0; i < trans[u].size(); ++i) { int v = trans[u][i]; if (v ^ fa[u]) { fa[v] = u, dfs1(v); size[u] += size[v]; if (!heavy[u] || size[v] > size[heavy[u]]) heavy[u] = v; } } } void dfs2(int u) { dfn[u] = ++idx; g[u][0] = 0, g[u][1] = val[u]; if (heavy[u]) { top[heavy[u]] = top[u]; dfs2(heavy[u]); bot[u] = bot[heavy[u]]; } else bot[u] = u; for (int i = 0; i < trans[u].size(); ++i) { int v = trans[u][i]; if (v == fa[u] || v == heavy[u]) continue; top[v] = v, dfs2(v); g[u][0] += std::max(f[v][0], f[v][1]); g[u][1] += f[v][0]; } f[u][0] = g[u][0] + std::max(f[heavy[u]][0], f[heavy[u]][1]); f[u][1] = g[u][1] + f[heavy[u]][0]; sgt.modify(1, 1, N, dfn[u], Matrix(g[u][0], g[u][1], g[u][0], -INF)); } Matrix Matrix::operator *(const Matrix &m) const { Matrix res; res.data[0][0] = std::max(data[0][0] + m.data[0][0], data[0][1] + m.data[1][0]); res.data[0][1] = std::max(data[0][0] + m.data[0][1], data[0][1] + m.data[1][1]); res.data[1][0] = std::max(data[1][0] + m.data[0][0], data[1][1] + m.data[1][0]); res.data[1][1] = std::max(data[1][0] + m.data[0][1], data[1][1] + m.data[1][1]); return res; } void SegmentTree::modify(int rt, int L, int R, int pos, const Matrix &m) { if (L == R) data[rt] = m; else { int mid = (L + R) >> 1; if (pos <= mid) modify(rt << 1, L, mid, pos, m); else modify(rt << 1 | 1, mid + 1, R, pos, m); data[rt] = data[rt << 1 | 1] * data[rt << 1];//注意乘的順序 } } void SegmentTree::query(int rt, int L, int R, int l, int r, Matrix &res) { if (L >= l && R <= r) res = res * data[rt];//注意乘的順序 else { int mid = (L + R) >> 1; if (r > mid) query(rt << 1 | 1, mid + 1, R, l, r, res); if (l <= mid) query(rt << 1, L, mid, l, r, res); } } void modify(int x, int y) { g[x][1] = g[x][1] - val[x] + y; val[x] = y; while (x) { int t = top[x], b = bot[x]; sgt.modify(1, 1, N, dfn[x], Matrix(g[x][0], g[x][1], g[x][0], -INF));//在線段樹中修改g Matrix tmp; sgt.query(1, 1, N, dfn[t], dfn[b], tmp);//查出鏈頂f的新值 g[fa[t]][0] -= std::max(f[t][0], f[t][1]);//更新鏈頂父親的g g[fa[t]][1] -= f[t][0]; f[t][0] = tmp.data[0][0], f[t][1] = tmp.data[0][1];//更新鏈頂 g[fa[t]][0] += std::max(f[t][0], f[t][1]); g[fa[t]][1] += f[t][0]; x = fa[t];//跳到鏈頂的父親 } } //Rhein_E