动态 DP 学习笔记

动态 DP 学习笔记

前置知识

矩阵乘法、线段树。

部分题目可能需要 \((\max,+)\) 矩阵乘法、树链剖分。

UPD(2023.1.13):可参考 NOIWC2023 游记 食用。

介绍

动态 DP 简称 DDP,一般是较简单的 DP,但是要求进行修改操作。

大致的思想是将 DP 的状态转移方程写作矩阵乘法或广义矩阵乘法的形式,然后使用线段树维护。

动态动态规划这名字好奇怪啊。

例题一:ABC246Ex 01? Queries

这并不是最经典的例题,但是十分适合用来理解动态 DP。

假设不带修改操作,容易想到 DP 解法:

\(f_{i,0/1}\) 表示考虑前 \(i\) 个位置,且以 \(0/1\) 结尾的本质不同非空子序列个数。

  • \(s_i=\texttt{0}\)\(f_{i,0}=f_{i-1,0}+f_{i-1,1}+1\)\(f_{i,1}=f_{i-1,1}\)
  • \(s_i=\texttt{1}\)\(f_{i,0}=f_{i-1,0}\)\(f_{i,1}=f_{i-1,0}+f_{i-1,1}+1\)
  • \(s_i=\texttt{?}\)\(f_{i,0}=f_{i-1,0}+f_{i-1,1}+1\)\(f_{i,1}=f_{i-1,0}+f_{i-1,1}+1\)

我们将上面的操作写成矩阵形式:

\[S_{a_i} \begin{bmatrix} f_{i-1,0}\\ f_{i-1,1}\\ 1\\ \end{bmatrix} = \begin{bmatrix} f_{i,0}\\ f_{i,1}\\ 1\\ \end{bmatrix} \]

则:

\[S_{\texttt{0}}= \begin{bmatrix} 1 & 1 & 1\\ 0 & 1 & 0\\ 0 & 0 & 1\\ \end{bmatrix}, S_{\texttt{1}}= \begin{bmatrix} 1 & 0 & 0\\ 1 & 1 & 1\\ 0 & 0 & 1\\ \end{bmatrix}, S_{\texttt{?}}= \begin{bmatrix} 1 & 1 & 1\\ 1 & 1 & 1\\ 0 & 0 & 1\\ \end{bmatrix} \]

答案为 \(f_{n,0}+f_{n,1}\) 可以如下求出:

\[\left(\prod\limits_{i=1}^nS_{a_i}\right) \begin{bmatrix} 0\\ 0\\ 1\\ \end{bmatrix} = \begin{bmatrix} f_{n,0}\\ f_{n,1}\\ 1\\ \end{bmatrix} \]

修改操作即为修改一个位置的转移矩阵,矩阵乘法满足结合律,因此使用线段树维护转移矩阵即可。

时间复杂度为 \(\Theta(N+Q\log Q)\)

参考代码
// Problem: AT_abc246_h [ABC246Ex] 01? Queries
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/AT_abc246_h
// Memory Limit: 1024 MB
// Time Limit: 6000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(ll x=(y);x<=(z);x++)
#define per(x,y,z) for(ll x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const ll N = 1e5+5, mod = 998244353;

ll n, m;
char s[N];
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
struct Matrix {
	ll x, y, a[3][3];
	Matrix(ll p=0, ll q=0) : x(p), y(q) {memset(a, 0, sizeof(a));}
	void print() {
		rep(i, 0, x-1) {
			printf("( ");
			rep(j, 0, y-1) printf("%d ", a[i][j]);
			printf(")\n");
		}
	}
	friend Matrix operator * (const Matrix& a, const Matrix& b) {
		Matrix c(a.x, b.y);
		rep(i, 0, a.x-1) {
			rep(j, 0, a.y-1) {
				rep(k, 0, b.y-1) {
					c.a[i][k] += a.a[i][j] * b.a[j][k] % mod;
					c.a[i][k] %= mod;
				}
			}
		}
		return c;
	}
}M0(3, 3), M1(3, 3), Mq(3, 3);
struct SegTree {
	Matrix t[N<<2];
	#define lc(u) (u<<1)
	#define rc(u) (u<<1|1)
	void pushup(ll u) {t[u] = t[lc(u)] * t[rc(u)];}
	void build(char* s, ll u, ll l, ll r) {
		if(l == r) {
			if(s[l] == '0') t[u] = M0;
			else if(s[l] == '1') t[u] = M1;
			else t[u] = Mq;
			// printf("%lld [%lld, %lld]\n", u, l, r);
			// t[u].print();
			return;
		}
		ll mid = (l + r) >> 1;
		build(s, lc(u), l, mid);
		build(s, rc(u), mid+1, r);
		pushup(u);
		// printf("%lld [%lld, %lld]\n", u, l, r);
		// t[u].print();
	}
	void modify(ll u, ll l, ll r, ll pos, const Matrix& k) {
		if(l == r) {
			t[u] = k;
			return;
		}
		ll mid = (l + r) >> 1;
		if(pos <= mid) modify(lc(u), l, mid, pos, k);
		else modify(rc(u), mid+1, r, pos, k);
		pushup(u);
	}
	#undef lc
	#undef rc
}sgt;

int main() {
	scanf("%lld%lld%s", &n, &m, s+1);
	// M0
	M0.a[0][0] = 1; M0.a[0][1] = 1; M0.a[0][2] = 1;
	M0.a[1][0] = 0; M0.a[1][1] = 1; M0.a[1][2] = 0;
	M0.a[2][0] = 0; M0.a[2][1] = 0; M0.a[2][2] = 1;
	// M1
	M1.a[0][0] = 1; M1.a[0][1] = 0; M1.a[0][2] = 0;
	M1.a[1][0] = 1; M1.a[1][1] = 1; M1.a[1][2] = 1;
	M1.a[2][0] = 0; M1.a[2][1] = 0; M1.a[2][2] = 1;
	// M?
	Mq.a[0][0] = 1; Mq.a[0][1] = 1; Mq.a[0][2] = 1;
	Mq.a[1][0] = 1; Mq.a[1][1] = 1; Mq.a[1][2] = 1;
	Mq.a[2][0] = 0; Mq.a[2][1] = 0; Mq.a[2][2] = 1;
	//
	sgt.build(s, 1, 1, n);
	while(m--) {
		ll p; char s[2];
		scanf("%lld%s", &p, s);
		if(s[0] == '0') sgt.modify(1, 1, n, p, M0);
		else if(s[0] == '1') sgt.modify(1, 1, n, p, M1);
		else sgt.modify(1, 1, n, p, Mq);
		Matrix ans(3, 1);
		ans.a[0][0] = 0;
		ans.a[1][0] = 0;
		ans.a[2][0] = 1;
		ans = sgt.t[1] * ans;
		// sgt.t[1].print();
		// ans.print();
		printf("%lld\n", (ans.a[0][0] + ans.a[1][0]) % mod);
	}
	return 0;
}

例题二:P4719 【模板】"动态 DP"&动态树分治

单点修改点权、求树上最大权独立集。经典动态 DP 例题。

若没有修改,容易得到 DP 解法:

\(f_{u,0/1}\) 表示考虑 \(u\) 子树,且必须不选/选 \(i\) 的最大权独立集。

  • \(f_{u,0}=\sum\limits_v\max\{f_{v,0},f_{v,1}\}\)
  • \(f_{u,1}=\sum\limits_vf_{v,0}+a_u\)

带修改的时候显然需要树链剖分,相当于从修改处向根跳重链,重链之间互相更新。

我们需要改变 DP 数组的定义,以迎合树链剖分时对轻重儿子的划分。

进一步地,设 \(g_{u,0}\) 表示不取 \(u\)\(u\) 的所有轻儿子可取可不取的最大权独立集,设 \(g_{u,1}\) 表示取 \(u\)\(u\) 的所有轻儿子都不取的最大权独立集,则:(\(v\)\(u\) 的重儿子)

  • \(f_{u,0}=g_{u,0}+\max\{f_{v,0},f_{v,1}\}\)
  • \(f_{u,1}=g_{u,1}+f_{v,0}\)

我们定义矩阵乘法不是原来的 \((+,\times)\) 规则,而是广义上的 \((\max,+)\) 规则,则有:

\[\begin{bmatrix} g_{u,0} & g_{u,0}\\ g_{u,1} & -\infty\\ \end{bmatrix} \begin{bmatrix} f_{v,0}\\ f_{v,1}\\ \end{bmatrix} = \begin{bmatrix} f_{u,0}\\ f_{u,1}\\ \end{bmatrix} \]

于是就做完了,但是代码巨难写。

时间复杂度为 \(\Theta(n+m\log^2n)\)

显然,使用 LCT 维护的话可以做到 \(\Theta(n+m\log n)\),但是 LCT 的常数巨大,和树剖谁跑得快我不好说。

另外还有一种叫做“全局平衡二叉树”的一只 \(\log\) 做法,但是我不会,因此请另请高明吧。

参考代码
// Problem: P4719 【模板】"动态 DP"&动态树分治
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4719
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=(y);x<=(z);x++)
#define per(x,y,z) for(int x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
const int N = 1e5+5, inf = 0x3f3f3f3f;

int n, m, a[N];
int f[N][2], g[N][2];
int fa[N], dis[N], sz[N], son[N], top[N], bot[N], dfn[N], id[N], tms;
vector<int> e[N];
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
struct Matrix {
	int x, y, a[2][2];
	Matrix(int p=0, int q=0) : x(p), y(q) {
		rep(i, 0, x-1) rep(j, 0, y-1) a[i][j] = -inf;
	}
	void print() {
		rep(i, 0, x-1) {
			printf("( ");
			rep(j, 0, y-1) printf("%d ", a[i][j]);
			printf(")\n");
		}
	}
	friend Matrix operator * (const Matrix& a, const Matrix& b) {
		Matrix c(a.x, b.y);
		rep(i, 0, a.x-1) rep(j, 0, a.y-1) rep(k, 0, b.y-1) chkmax(c.a[i][k], a.a[i][j] + b.a[j][k]);
		return c;
	}
}val[N];
struct SegTree {
	Matrix t[N<<2];
	#define lc(u) (u<<1)
	#define rc(u) (u<<1|1)
	void pushup(int u) {t[u] = t[lc(u)] * t[rc(u)];}
	void build(int u, int l, int r) {
		if(l == r) {
			t[u] = val[id[l]];
			// printf("%d [%d, %d]\n", u, l, r);
			// t[u].print();
			return;
		}
		int mid = (l + r) >> 1;
		build(lc(u), l, mid);
		build(rc(u), mid+1, r);
		pushup(u);
		// printf("%d [%d, %d]\n", u, l, r);
		// t[u].print();
	}
	void modify(int u, int l, int r, int pos) {
		if(l == r) {
			t[u] = val[id[l]];
			return;
		}
		int mid = (l + r) >> 1;
		if(pos <= mid) modify(lc(u), l, mid, pos);
		else modify(rc(u), mid+1, r, pos);
		pushup(u);
	}
	Matrix query(int u, int l, int r, int ql, int qr) {
		if(ql <= l && r <= qr) return t[u];
		int mid = (l + r) >> 1;
		if(qr <= mid) return query(lc(u), l, mid, ql, qr);
		if(ql > mid) return query(rc(u), mid+1, r, ql, qr);
		return query(lc(u), l, mid, ql, qr) * query(rc(u), mid+1, r, ql, qr);
	}
	#undef lc
	#undef rc
}sgt;
void dfs1(int u, int f) {
	fa[u] = f;
	dis[u] = dis[f] + 1;
	sz[u] = 1;
	for(int v : e[u]) {
		if(v != f) {
			dfs1(v, u);
			sz[u] += sz[v];
			if(sz[v] > sz[son[u]]) son[u] = v;
		}
	}
}
void dfs2(int u, int tp) {
	top[u] = tp; bot[u] = u;
	dfn[u] = ++tms; id[tms] = u;
	f[u][0] = 0; f[u][1] = a[u];
	g[u][0] = 0; g[u][1] = a[u];
	if(son[u]) {
		dfs2(son[u], tp);
		f[u][0] += max(f[son[u]][0], f[son[u]][1]);
		f[u][1] += f[son[u]][0];
		bot[u] = bot[son[u]];
		for(int v : e[u]) {
			if(v != fa[u] && v != son[u]) {
				dfs2(v, v);
				f[u][0] += max(f[v][0], f[v][1]);
				f[u][1] += f[v][0];
				g[u][0] += max(f[v][0], f[v][1]);
				g[u][1] += f[v][0];
			}
		}
	}
	val[u] = Matrix(2, 2);
	val[u].a[0][0] = g[u][0]; val[u].a[0][1] = g[u][0];
	val[u].a[1][0] = g[u][1]; val[u].a[1][1] = -inf;
	// printf("%d\n", u);
	// val[u].print();
}
void modifyChain(int u, int w) {
	g[u][1] += w - a[u];
	a[u] = w;
	val[u].a[1][0] = g[u][1];
	while(u) {
		Matrix bef = sgt.query(1, 1, n, dfn[top[u]], dfn[bot[u]]);
		sgt.modify(1, 1, n, dfn[u]);
		Matrix aft = sgt.query(1, 1, n, dfn[top[u]], dfn[bot[u]]);
		u = fa[top[u]];
		g[u][0] += max(aft.a[0][0], aft.a[1][0]) - max(bef.a[0][0], bef.a[1][0]);
		g[u][1] += aft.a[0][0] - bef.a[0][0];
		val[u].a[0][0] = g[u][0]; val[u].a[0][1] = g[u][0];
		val[u].a[1][0] = g[u][1]; val[u].a[1][1] = -inf;
	}
}

int main() {
	scanf("%d%d", &n, &m);
	rep(i, 1, n) scanf("%d", &a[i]);
	rep(i, 1, n-1) {
		int u, v;
		scanf("%d%d", &u, &v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs1(1, 0);
	dfs2(1, 1);
	sgt.build(1, 1, n);
	while(m--) {
		int p, x;
		scanf("%d%d", &p, &x);
		modifyChain(p, x);
		Matrix ans = sgt.query(1, 1, n, dfn[1], dfn[bot[1]]);
		printf("%d\n", max(ans.a[0][0], ans.a[1][0]));
	}
	return 0;
}
posted @ 2022-12-15 17:09  rui_er  阅读(1673)  评论(0)    收藏  举报