【知识总结】动态 DP
勾起了我悲伤的回忆 —— NOIP2018 316pts ……
主要思想:将 DP 过程分解为方便单点修改和一个区间合并的操作(通常类似矩阵乘法),然后用数据结构(通常为线段树)维护。
例:给定一个长为 \(n\) 的整数序列,相邻两个数最多选一个,有 \(m\) 次修改序列中的一个数,求每次修改后选出数之和的最大值。
\(n,m\leq 10^5\) 。
如果不会做不带修改的情况,请默默摁 Ctrl + w 然后去学 DP 入门
如果不带修改,明显设 \(f_{i,0/1}\) 表示当第 \(i\) 个点选 (0) / 不选 (1) 时,前 \(i\) 个点的和的最大值。于是有如下转移方程:
\]
\]
如果加入修改操作呢?只有这两个 DP 方程比较难办,因为修改一个值就要重新计算后面的所有答案。GG
接下来是「动态 DP 」中最巧妙的部分:考虑用一个矩阵来表示从 \(i-1\) 点向 \(i\) 点转移,用某个表示「初始状态」的矩阵依次乘上每个点的转移就是答案。因为矩阵乘法有结合律,所以可以把答案表示成「初始状态」乘上「修改点前面的矩阵乘积」乘上「当前位置修改后的矩阵」乘上「修改点后面的矩阵乘积」。这样只需要用线段树单点修改和查询区间乘积(事实上这道题只需要查全局乘积)即可。
然而,这道题中转移的运算并不是加和乘,尤其是其中还有一个碍眼的求最大值。但我们可以把矩阵乘法的定义稍加修改,把原来两个整数的「乘法」改为两个整数的加法,「加法」改为对两个整数取最大值。这样我们就构造如下转移矩阵:
f_{i-1,0}&f_{i-1,1}
\end{bmatrix}
\begin{bmatrix}
0&a_i\\
0&-\infty\\
\end{bmatrix}=
\begin{bmatrix}
f_{i,0}&f_{i,1}\\
\end{bmatrix}\]
还有一个很多人没考虑过的细节 (可能是大佬们认为这个问题太显然不需要考虑) :这个「初始状态」是什么呢?对于这道题,前一个数如果不选是不影响当前决策的,而如果选了的话就会造成一个当前点不能选的「约束」。而第一个点无论如何都不会受到这种「约束」,所以第一个点的「前一个点」应该被看作「没有选」,即初始状态为 \(\begin{bmatrix}0&-\infty\end{bmatrix}\) 。
我们把这个问题扩展到树上,即每条边的两端点中至少选一个点(洛谷 4719【模板】动态 DP )。考虑树链剖分来转化成序列问题。设 \(f_{i,0/1}\) 表示 \(i\) 点选 / 不选时 \(i\) 点子树中的最大权值和,\(g_{i,0/1}\) 表示 \(i\) 点选 / 不选时 \(i\) 点子树除 \(s_i\) 的子树以外的部分中的最大权值和,其中 \(s_i\) 是 \(i\) 的重儿子。对于一条重链有如下方程:
f_{s_i,0}&f_{s_i,1}
\end{bmatrix}
\begin{bmatrix}
g_{i,0}&g_{i,1}\\
g_{i,0}&-\infty\\
\end{bmatrix}=
\begin{bmatrix}
f_{i,0}&f_{i,1}\\
\end{bmatrix}\]
这样,每个点的答案是「初始状态」乘上它到所在重链末尾的矩阵乘积。
至于具体实现,可以开始先一遍 DP 算出所有的 \(f\) 和 \(g\) 。每次修改时沿着重链向上爬,暴力修改链首父亲的 \(g\) 值。链首到链首父亲的边是一条轻边,所以这样每次修改一个点时要更新 \(g\) 值的点的数量约等于当前点到根的路径上的轻边数量(可能有加一减一之类的细节),是 \(O(\log n)\) 。因此总复杂度 \(O(mlog^2n)\) 。
和上面类似的分析,初始状态(叶子节点那个不存在的重儿子的 \(f\) 值)是 \(\begin{bmatrix}0&-\infty\end{bmatrix}\) 。用这个东西去乘相当于取原矩阵的第一行,所以不需要「显式」地乘。
代码:
很抱歉我代码里的矩阵行列和上文是反的,所有矩阵乘法的顺序也是反的我也不知道怎么回事 QAQ 。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
namespace zyt
{
	template<typename T>
	inline bool read(T &x)
	{
		char c;
		bool f = false;
		x = 0;
		do
			c = getchar();
		while (c != EOF && c != '-' && !isdigit(c));
		if (c == EOF)
			return false;
		if (c == '-')
			f = true, c = getchar();
		do
			x = x * 10 + c - '0', c = getchar();
		while (isdigit(c));
		if (f)
			x = -x;
		return true;
	}
	template<typename T>
	inline void write(T x)
	{
		static char buf[20];
		char *pos = buf;
		if (x < 0)
			putchar('-'), x = -x;
		do
			*pos++ = x % 10 + '0';
		while (x /= 10);
		while (pos > buf)
			putchar(*--pos);
	}
	const int N = 1e5 + 10, INF = 0x3f3f3f3f;
	int n, m, head[N], ecnt, w[N], size[N], son[N], fa[N], dfn[N], dfncnt, top[N], f[N][2], g[N][2], end[N], pos[N];
	struct edge
	{
		int to, next;
	}e[N << 1];
	void add(const int a, const int b)
	{
		e[ecnt] = (edge){b, head[a]}, head[a] = ecnt++;
	}
	void dfs(const int u, const int f)
	{
		fa[u] = f, size[u] = 1;
		for (int i = head[u]; ~i; i = e[i].next)
		{
			int v = e[i].to;
			if (v == f)
				continue;
			dfs(v, u);
			size[u] += size[v];
			if (size[v] > size[son[u]])
				son[u] = v;
		}
	}
	void dfs2(const int u, const int t)
	{
		top[u] = t, dfn[u] = ++dfncnt, pos[dfncnt] = u, end[t] = u;
		if (son[u])
			dfs2(son[u], t);
		for (int i = head[u]; ~i; i = e[i].next)
		{
			int v = e[i].to;
			if (v == fa[u] || v == son[u])
				continue;
			dfs2(v, v);
		}
	}
	void dfs3(const int u)
	{
		g[u][0] = 0, g[u][1] = w[u];
		for (int i = head[u]; ~i; i = e[i].next)
		{
			int v = e[i].to;
			if (v == fa[u] || v == son[u])
				continue;
			dfs3(v);
			g[u][0] += max(f[v][0], f[v][1]);
			g[u][1] += f[v][0];
		}
		f[u][0] = g[u][0], f[u][1] = g[u][1];
		if (son[u])
		{
			dfs3(son[u]);
			f[u][0] += max(f[son[u]][0], f[son[u]][1]);
			f[u][1] += f[son[u]][0];
		}
	}
	struct Matrix
	{
		int data[2][2], n, m;
		Matrix(const int _n = 0, const int _m = 0)
			: n(_n), m(_m)
		{
			for (int i = 0; i < n; i++)
				for (int j = 0; j < m; j++)
					data[i][j] = -INF;
		}
		Matrix operator * (const Matrix &b) const
		{
			Matrix ans(n, b.m);
			for (int i = 0; i < n; i++)
				for (int k = 0; k < m; k++)
					for (int j = 0; j < b.m; j++)
						ans.data[i][j] = max(ans.data[i][j], data[i][k] + b.data[k][j]);
			return ans;
		}
	}val[N];
	namespace Segment_Tree
	{
		struct node
		{
			Matrix m;
		}tree[N << 2];
		void update(const int rot)
		{
			tree[rot].m = tree[rot << 1].m * tree[rot << 1 | 1].m;
		}
		void build(const int rot, const int lt, const int rt)
		{
			tree[rot].m = Matrix(2, 2);
			if (lt == rt)
				return void(tree[rot].m = val[pos[lt]]);
			int mid = (lt + rt) >> 1;
			build(rot << 1, lt, mid), build(rot << 1 | 1, mid + 1, rt);
			update(rot);
		}
		void change(const int rot, const int lt, const int rt, const int p)
		{
			if (lt == rt)
				return void(tree[rot].m = val[pos[p]]);
			int mid = (lt + rt) >> 1;
			if (p <= mid)
				change(rot << 1, lt, mid, p);
			else
				change(rot << 1 | 1, mid + 1, rt, p);
			update(rot);
		}
		Matrix query(const int rot, const int lt, const int rt, const int ls, const int rs)
		{
			if (ls <= lt && rt <= rs)
				return tree[rot].m;
			int mid = (lt + rt) >> 1;
			if (rs <= mid)
				return query(rot << 1, lt, mid, ls, rs);
			else if (ls > mid)
				return query(rot << 1 | 1, mid + 1, rt, ls, rs);
			else
				return query(rot << 1, lt, mid, ls, rs) * query(rot << 1 | 1, mid + 1, rt, ls, rs);
		}
	}
	int work()
	{
		using namespace Segment_Tree;
		read(n), read(m);
		memset(head, -1, sizeof(int[n + 1]));
		for (int i = 1; i <= n; i++)
			read(w[i]), val[i] = Matrix(2, 2);
		for (int i = 1; i < n; i++)
		{
			int a, b;
			read(a), read(b);
			add(a, b), add(b, a);
		}
		dfs(1, 0), dfs2(1, 1), dfs3(1);
		for (int i = 1; i <= n; i++)
			val[i].data[0][0] = val[i].data[0][1] = g[i][0], val[i].data[1][0] = g[i][1], val[i].data[1][1] = -INF;
		build(1, 1, n);
		while (m--)
		{
			int u, x;
			read(u), read(x);
			val[u].data[1][0] += x - w[u];
			w[u] = x;
			Matrix a, b;
			while (u)
			{
				a = query(1, 1, n, dfn[top[u]], dfn[end[top[u]]]);
				change(1, 1, n, dfn[u]);
				b = query(1, 1, n, dfn[top[u]], dfn[end[top[u]]]);
				u = fa[top[u]];
				val[u].data[0][0] += max(b.data[0][0], b.data[1][0]) - max(a.data[0][0], a.data[1][0]);
				val[u].data[0][1] = val[u].data[0][0];
				val[u].data[1][0] += b.data[0][0] - a.data[0][0];
			}
			Matrix ans = query(1, 1, n, dfn[1], dfn[end[1]]);
			write(max(ans.data[0][0], ans.data[1][0])), putchar('\n');
		}
		return 0;
	}
}
int main()
{
#ifdef BlueSpirit
	freopen("4719.in", "r", stdin);
#endif
	return zyt::work();
}
【知识总结】动态 DP的更多相关文章
- 动态DP之全局平衡二叉树
		目录 前置知识 全局平衡二叉树 大致介绍 建图过程 修改过程 询问过程 时间复杂度的证明 板题 前置知识 在学习如何使用全局平衡二叉树之前,你首先要知道如何使用树链剖分解决动态DP问题.这里仅做一个简 ... 
- [动态dp]线段树维护转移矩阵
		背景:czy上课讲了新知识,从未见到过,总结一下. 所谓动态dp,是在动态规划的基础上,需要维护一些修改操作的算法. 这类题目分为如下三个步骤:(都是对于常系数齐次递推问题) 1先不考虑修改,不考虑区 ... 
- 【LOJ511】[LibreOJ NOI Round #1]验题(动态DP)
		我这道题写了整!整!三!天! 我要一定要写这篇博客来表达我复!杂!的!心!情! 题目 LOJ511 官方题解(这个题解似乎不是很详细,我膜 std 才看懂的) 调这道题验证了我校某人的一句话:调题是一 ... 
- 【模板】动态 DP
		luogu传送门. 最近学了一下动态dp,感觉没有想象的难. 动态DP simple的DP是这样的: 给棵树,每个点给个权值,求一下最大权独立集. 动态DP是这样的: 给棵树,每个点给个权值还到处改, ... 
- Luogu P4643 【模板】动态dp
		题目链接 Luogu P4643 题解 猫锟在WC2018讲的黑科技--动态DP,就是一个画风正常的DP问题再加上一个动态修改操作,就像这道题一样.(这道题也是PPT中的例题) 动态DP的一个套路是把 ... 
- 动态dp学习笔记
		我们经常会遇到一些问题,是一些dp的模型,但是加上了什么待修改强制在线之类的,十分毒瘤,如果能有一个模式化的东西解决这类问题就会非常好. 给定一棵n个点的树,点带点权. 有m次操作,每次操作给定x,y ... 
- 洛谷P4719 动态dp
		动态DP其实挺简单一个东西. 把DP值的定义改成去掉重儿子之后的DP值. 重链上的答案就用线段树/lct维护,维护子段/矩阵都可以.其实本质上差不多... 修改的时候在log个线段树上修改.轻儿子所在 ... 
- 动态 DP 学习笔记
		不得不承认,去年提高组 D2T3 对动态 DP 起到了良好的普及效果. 动态 DP 主要用于解决一类问题.这类问题一般原本都是较为简单的树上 DP 问题,但是被套上了丧心病狂的修改点权的操作.举个例子 ... 
- 动态dp初探
		动态dp初探 动态区间最大子段和问题 给出长度为\(n\)的序列和\(m\)次操作,每次修改一个元素的值或查询区间的最大字段和(SP1714 GSS3). 设\(f[i]\)为以下标\(i\)结尾的最 ... 
随机推荐
- 对于模块加载:ES6、CommonJS、AMD、CMD的区别
			运行和编译的概念 编译包括编译和链接两步. 编译,把源代码翻译成机器能识别的代码或者某个中间状态的语言. 比如java只有JVM识别的字节码,C#中只有CLR能识别的MSIL.还简单的作一些比如检查有 ... 
- Java项目使用 Tomcat 部署 Linux 服务器
			一.安装 Java 环境 1 下载 jdk 8 yum -y install java-1.8.0-openjdk java-1.8.0-openjdk-devel 设置 centos7 环境变量 v ... 
- 含-SH的ACE抑制药的青霉胺样反应
			关于 含-SH的血管紧张素转化酶(ACE)抑制药如卡托普利具有青霉胺样反应.而依那普利则不含-SH. 青霉胺样反应 青霉胺样反应,指应用含-SH的ACE抑制药产生的皮疹.嗜酸性粒细胞(E)增多.味觉异 ... 
- Problem F. Wiki with String
			Problem F. Wiki with StringInput file: standard input Time limit: 1 secondOutput file: standard outp ... 
- webuploader解决大文件断点续传
			文件夹数据库处理逻辑 public class DbFolder { JSONObject root; public DbFolder() { this.root = new JSONObject() ... 
- php . extension_loaded
			(PHP 4, PHP 5, PHP 7) extension_loaded — 检查一个扩展是否已经加载 如果 name 指定的扩展已加载,返回TRUE,否则返回 FALSE. Example #1 ... 
- 带状矩阵的存储(c++)
			2 1 0 0 3 1 3 0 0 5 2 7 0 0 9 0 这个程序对于三对角矩阵都是有效的,为了精 ... 
- /usr/bin/xauth: file /home/user/.Xauthority does not exist
			错误信息如下: /usr/bin/xauth: file /home/user/.Xauthority does not exist 错误原因:是因为添加用户时没有授权对应的目录,仅仅执行了usera ... 
- Qt中文编码和QString类Unicode编码转换
			版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/g423tgl234/article ... 
- mysql 常用字符串操作
			SET @L=16, @i=3;SELECT *,CONCAT( LEFT(tag2,@i-1) ,'W', RIGHT(tag2,@L-@i)) from tb_main LIMIT 1,10; 
