动态 DP 学习笔记
动态 DP 学习笔记
前置知识
矩阵乘法、线段树。
部分题目可能需要 矩阵乘法、树链剖分。
UPD(2023.1.13):可参考 NOIWC2023 游记 食用。
介绍
动态 DP 简称 DDP,一般是较简单的 DP,但是要求进行修改操作。
大致的思想是将 DP 的状态转移方程写作矩阵乘法或广义矩阵乘法的形式,然后使用线段树维护。
动态动态规划这名字好奇怪啊。
例题一:ABC246Ex 01? Queries
这并不是最经典的例题,但是十分适合用来理解动态 DP。
假设不带修改操作,容易想到 DP 解法:
设 表示考虑前 个位置,且以 结尾的本质不同非空子序列个数。
- 若 :,。
- 若 :,。
- 若 :,。
我们将上面的操作写成矩阵形式:
则:
答案为 可以如下求出:
修改操作即为修改一个位置的转移矩阵,矩阵乘法满足结合律,因此使用线段树维护转移矩阵即可。
时间复杂度为 。
参考代码
// 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 解法:
表示考虑 子树,且必须不选/选 的最大权独立集。
- 。
- 。
带修改的时候显然需要树链剖分,相当于从修改处向根跳重链,重链之间互相更新。
我们需要改变 DP 数组的定义,以迎合树链剖分时对轻重儿子的划分。
进一步地,设 表示不取 、 的所有轻儿子可取可不取的最大权独立集,设 表示取 、 的所有轻儿子都不取的最大权独立集,则:( 为 的重儿子)
我们定义矩阵乘法不是原来的 规则,而是广义上的 规则,则有:
于是就做完了,但是代码巨难写。
时间复杂度为 。
显然,使用 LCT 维护的话可以做到 ,但是 LCT 的常数巨大,和树剖谁跑得快我不好说。
另外还有一种叫做“全局平衡二叉树”的一只 做法,但是我不会,因此请另请高明吧。
参考代码
// 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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现