动态 DP 学习笔记

动态 DP 学习笔记

前置知识

矩阵乘法、线段树。

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

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

介绍

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

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

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

例题一:ABC246Ex 01? Queries

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

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

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

  • si=0fi,0=fi1,0+fi1,1+1fi,1=fi1,1
  • si=1fi,0=fi1,0fi,1=fi1,0+fi1,1+1
  • si=?fi,0=fi1,0+fi1,1+1fi,1=fi1,0+fi1,1+1

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

Sai[fi1,0fi1,11]=[fi,0fi,11]

则:

S0=[111010001],S1=[100111001],S?=[111111001]

答案为 fn,0+fn,1 可以如下求出:

(i=1nSai)[001]=[fn,0fn,11]

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

时间复杂度为 Θ(N+QlogQ)

参考代码
// 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 解法:

fu,0/1 表示考虑 u 子树,且必须不选/选 i 的最大权独立集。

  • fu,0=vmax{fv,0,fv,1}
  • fu,1=vfv,0+au

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

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

进一步地,设 gu,0 表示不取 uu 的所有轻儿子可取可不取的最大权独立集,设 gu,1 表示取 uu 的所有轻儿子都不取的最大权独立集,则:(vu 的重儿子)

  • fu,0=gu,0+max{fv,0,fv,1}
  • fu,1=gu,1+fv,0

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

[gu,0gu,0gu,1][fv,0fv,1]=[fu,0fu,1]

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

时间复杂度为 Θ(n+mlog2n)

显然,使用 LCT 维护的话可以做到 Θ(n+mlogn),但是 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 @   rui_er  阅读(1007)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示