\(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