[学习笔记]动态动态规划/动态DP/DDP

概述

\(DDP\)是指一类需要支持修改的\(DP\)问题,常见的主要是带修树形\(DP\),可以用树链剖分结合矩阵乘法优化修改的复杂度

详解

从例题来分析:洛谷P4719

题目大意:给出\(n\)个点的树,每个点有点权,共\(m\)次操作,每次修改一个点的点权,求每次修改后树的最大权独立集的权值大小

\(n, m \le 1e5\)


如果不带修改,容易想到设\(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)\)

既然修改的是一条路径,不妨试试树链剖分

但是按上面的方式转移显然不能将一条链上的转移合并,所以考虑重新设计一下状态,\(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} \]

观察后两式中\(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] \]

容易证明新定义的矩阵乘法具有结合律,那么就可以树链剖分后用线段树维护重链上第二个矩阵的积

于是我们可以发现:

  1. 如果一个点是链顶,它的\(f\)会对它父亲的\(g\)产生贡献
  2. 否则,它的\(g\)会对它所在链的链顶产生贡献,且已经统计进线段树中

所以我们只需要线段树维护\(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
posted @ 2019-04-08 14:52  Rhein_E  阅读(811)  评论(0编辑  收藏  举报