[洛谷 P4719] 【模板】“动态 DP“&动态树分治(树链剖分 + 矩阵) | 错题本

文章目录

题目

【模板】“动态 DP”&动态树分治

分析

首先写出朴素的 DP 式: f [ u ] [ 0 / 1 ] f[u][0/1] f[u][0/1] 表示不选 / / / u u u 点, u u u 的子树内的最大权独立集的权值和。设 v v v u u u 的儿子(下同),则 f [ u ] [ 0 ] = ∑ v max ⁡ { f [ v ] [ 0 ] , f [ v ] [ 1 ] } f [ u ] [ 1 ] = ∑ v f [ v ] [ 0 ] + a u \begin{aligned}f[u][0] &= \sum_{v} \max\{f[v][0], f[v][1]\} \\ f[u][1] &= \sum_{v} f[v][0] + a_u\end{aligned} f[u][0]f[u][1]=vmax{f[v][0],f[v][1]}=vf[v][0]+au 用树链剖分将树转化成一个线性结构维护动态 DP 的转移矩阵。具体来说,需要将转移矩阵设计成可以由重链传递的信息,因此设 h h h u u u 的重儿子,且 g [ u ] [ 0 ] = ∑ v ≠ h max ⁡ { f [ v ] [ 0 ] , f [ v ] [ 1 ] } g [ u ] [ 1 ] = ∑ v ≠ h f [ v ] [ 0 ] + a u \begin{aligned} g[u][0] &= \sum_{v \neq h} \max\{f[v][0], f[v][1]\} \\ g[u][1] &= \sum_{v \neq h} f[v][0] + a_u\end{aligned} g[u][0]g[u][1]=v=hmax{f[v][0],f[v][1]}=v=hf[v][0]+au 于是 f [ u ] [ 0 ] = g [ u ] [ 0 ] + max ⁡ { f [ h ] [ 0 ] , f [ h ] [ 1 ] } f [ u ] [ 1 ] = g [ u ] [ 1 ] + f [ h ] [ 0 ] \begin{aligned} f[u][0] &= g[u][0] + \max\{f[h][0], f[h][1]\} \\ f[u][1] &= g[u][1] + f[h][0] \end{aligned} f[u][0]f[u][1]=g[u][0]+max{f[h][0],f[h][1]}=g[u][1]+f[h][0] 转化一下 f [ u ] [ 0 ] = max ⁡ { g [ u ] [ 0 ] + f [ h ] [ 0 ] , g [ u ] [ 0 ] + f [ h ] [ 1 ] } f [ u ] [ 1 ] = max ⁡ { g [ u ] [ 1 ] + f [ h ] [ 0 ] , − ∞ } \begin{aligned} f[u][0] &= \max\{g[u][0] + f[h][0], g[u][0] + f[h][1]\} \\ f[u][1] &= \max\{g[u][1] + f[h][0], -\infty\}\end{aligned} f[u][0]f[u][1]=max{g[u][0]+f[h][0],g[u][0]+f[h][1]}=max{g[u][1]+f[h][0],} 就变成了喜闻乐见的矩阵“乘法”形式。将常规乘法重新定义 + → max ⁡ , × → + + \to \max, \times \to + +max,×+,于是有 ( g [ u ] [ 0 ] g [ u ] [ 0 ] g [ u ] [ 1 ] − ∞ ) × ( f [ h ] [ 0 ] f [ h ] [ 1 ] ) = ( f [ u ] [ 0 ] f [ u ] [ 1 ] ) \begin{pmatrix} g[u][0] & g[u][0] \\ g[u][1] & -\infty \end{pmatrix} \times \begin{pmatrix} f[h][0] \\ f[h][1] \end{pmatrix} = \begin{pmatrix} f[u][0] \\ f[u][1] \end{pmatrix} (g[u][0]g[u][1]g[u][0])×(f[h][0]f[h][1])=(f[u][0]f[u][1]) 于是我们可以在重链上维护 2 × 2 2 \times 2 2×2 的转移矩阵的乘积,事实上 g g g 数组将将重链周围的轻儿子的信息整合到了该重链上,这样一层一层传递,最终只需要计算以 1 1 1 为顶的重链的转移矩阵乘积,就得到了整个树的最大权独立集。

对于更改,我们依次由重链向上跳,每次对一个重链的 g g g 单点修改即可。
如图,假设我们要修改最下面的一个橙色点。粗线是重链,细线是轻边,蓝色箭头表示了我们的访问过程,橙色元素是需要在线段树上单点修改的元素。
图

代码

注意一下更新 g g g 不好直接覆盖,而是给其一个增量。另外本题中,观察转移矩阵乘法的方向,DP 的方向是由叶子到根,对应的转移矩阵是从右至左乘的,但是在线段树上叶子到根 DFN 递减,因此线段树合并儿子结点和查询是 lchrch。写动态 DP 一定要注意乘法的方向,因为矩阵乘法没有交换律!例如 [CodeForces 750E] New Year and Old Subsequence 这道题就是 rchlch

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>

const int INF = 0x3f3f3f3f;
const int MAXN = 100000;
const int MAXP = 2;

int N, Q;
int A[MAXN + 5];
std::vector<int> G[MAXN + 5];

struct Matrix {
	int n, m;
	int Mat[MAXP + 1][MAXP + 1];

	Matrix(int _n = 2, int _m = 2) {
		n = _n, m = _n;
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				Mat[i][j] = -INF;
	}

	int* operator [] (const int &i) {
		return Mat[i];
	}

	Matrix operator * (Matrix other) {
		Matrix ret(n, other.m);
		for (int i = 1; i <= ret.n; i++)
			for (int j = 1; j <= ret.m; j++)
				for (int k = 1; k <= m; k++)
					ret[i][j] = std::max(ret[i][j], Mat[i][k] + other[k][j]);
		return ret;
	}

	void Debug() {
		printf("n = %d, m = %d\n", n, m);
		for (int i = 1; i <= n; i++, puts(""))
			for (int j = 1; j <= m; j++)
				printf("%d ", Mat[i][j]);
	}
}H[MAXN + 5];

int Dep[MAXN + 5];
int Top[MAXN + 5], End[MAXN + 5];
int Dfn[MAXN + 5], Tid[MAXN + 5], DfnCnt;
int Size[MAXN + 5], Son[MAXN + 5], Fa[MAXN + 5];

struct SegmentTree {
	#define lch (u << 1)
	#define rch (u << 1 | 1)

	Matrix Trans[(MAXN << 2) + 5];

	void PushUp(int u) {
		Trans[u] = Trans[lch] * Trans[rch];
	}

	void Build(int u, int lft, int rgt) {
		if (lft == rgt) {
			Trans[u] = H[Tid[lft]];
			return;
		}
		int mid = (lft + rgt) >> 1;
		Build(lch, lft, mid);
		Build(rch, mid + 1, rgt);
		PushUp(u);
	}

	void Modify(int u, int lft, int rgt, int pos) {
		if (lft == rgt) {
			Trans[u] = H[Tid[pos]];
			return;
		}
		int mid = (lft + rgt) >> 1;
		if (pos <= mid) Modify(lch, lft, mid, pos);
		else Modify(rch, mid + 1, rgt, pos);
		PushUp(u);
	}
  
  Matrix Query(int u, int lft, int rgt, int l, int r) {
		if (l <= lft && rgt <= r)
			return Trans[u];
		int mid = (lft + rgt) >> 1;
		if (l > mid) return Query(rch, mid + 1, rgt, l, r);
		if (r <= mid) return Query(lch, lft, mid, l, r);
		return Query(lch, lft, mid, l, r) * Query(rch, mid + 1, rgt, l, r);
	}
}T;

void Dfs1(int u, int fa) {
	int Max = 0;
	Fa[u] = fa; Size[u] = 1;
	for (int i = 0; i < int(G[u].size()); i++) {
		int v = G[u][i];
		if (v != fa) {
			Dfs1(v, u);
			Size[u] += Size[v];
			if (Size[v] > Max)
				Max = Size[Son[u] = v];
		}
	}
}

int Dp[MAXN + 5][2];

#define F0(i) Dp[i][0]
#define F1(i) Dp[i][1]
#define G0(i) H[i][1][1]
#define G1(i) H[i][2][1]

void Dfs2(int u, int top) {
	Top[Tid[Dfn[u] = ++DfnCnt] = u] = top, End[Top[u]] = Dfn[u];
	F0(u) = G0(u) = 0, F1(u) = G1(u) = A[u];
	if (Son[u]) {
		Dfs2(Son[u], top);
		F1(u) += F0(Son[u]);
		F0(u) += std::max(F0(Son[u]), F1(Son[u]));
	}
	for (int i = 0; i < int(G[u].size()); i++) {
		int v = G[u][i];
		if (v != Fa[u] && v != Son[u]) {
			Dfs2(v, v);
			G1(u) += F0(v), G0(u) += std::max(F0(v), F1(v));
			F1(u) += F0(v), F0(u) += std::max(F0(v), F1(v));
		}
	}
	H[u][1][2] = H[u][1][1];
}

void Modify(int u, int x) { 
	H[u][2][1] += x - A[u], A[u] = x;
	while (u) {
		Matrix x = T.Query(1, 1, N, Dfn[Top[u]], End[Top[u]]);
		T.Modify(1, 1, N, Dfn[u]);
		Matrix y = T.Query(1, 1, N, Dfn[Top[u]], End[Top[u]]);
		u = Fa[Top[u]];
		G0(u) += std::max(y[1][1], y[2][1]) - std::max(x[1][1], x[2][1]);
		H[u][1][2] = H[u][1][1];
		G1(u) += y[1][1] - x[1][1];
	}
}

int main() {
	scanf("%d%d", &N, &Q);
	for (int i = 1; i <= N; i++)
		scanf("%d", &A[i]);
	for (int i = 1; i < N; i++) {
		int u, v; scanf("%d%d", &u, &v);
		G[u].push_back(v); G[v].push_back(u);
	}
	Dfs1(1, 0);
	Dfs2(1, 1);
	T.Build(1, 1, N);
	while (Q--) {
		int u, x;
		scanf("%d%d", &u, &x);
		Modify(u, x);
		Matrix Ans = T.Query(1, 1, N, 1, End[1]);
		printf("%d\n", std::max(Ans[1][1], Ans[2][1]));
	}
	return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值