浅谈动态 dp

news/2025/3/13 3:17:33/文章来源:https://www.cnblogs.com/paper-book/p/18765965

DDP,即动态动态规划,一般是指在原有的 DP 模型上不断对初始值进行修改并得出答案。

一般而言都是进行单点修改,并且搭配数据结构以及矩阵进行实现。

这里要先给出一个前置知识:

广义矩阵乘法

原矩阵乘法式子为 \(C_{i,j}=\sum_kA_{i,k}\times B_{k,j}\)

其实写成这样同样是满足矩阵乘法的性质的:\(C_{i,j}=\max_k(A_{i,k}+B_{k,j})\)

事实上只要广义上的 \(+\) 满足交换律\(\times\) 满足交换律结合律,且 \(\times\)\(+\) 存在分配律时矩阵乘法都是有结合律和分配律的性质的。

接下来进入正题:

动态DP

先看一道例题:

GSS 3 - Can you answer these queries III

给定一个长度为 \(n\) 的序列 \(A\) ,以及 \(m\) 次操作,操作有两种类型:

  • 1,x,y:将 \(A_x\) 修改为 \(y\)
  • 2,l,r:求 \(\max\{A[i]+A[i+1]+···+A[j]\}\ (l \le i \le j\le r)\)

其实就是单点修改最大子段和。

显然可以线段树为维护前缀最大和与后缀最大和,但是我们今天讨论另一种解法:动态 DP。

先想一想如果没有修改并且是单纯求全局最大子段和是怎么求的。

转移式子显然是 \(dp_i=\max(dp_{i-1}+a_i,a_i)\)

发现可以用一开头介绍的 \(\max+\) 矩阵来完成这个转移式:

\[\begin{bmatrix}dp_{i-1}\\0\end{bmatrix}\times\begin{bmatrix}a_i & -\infty \\a_i & 0\end{bmatrix}=\begin{bmatrix}dp_{i}\\0\end{bmatrix} \]

因此可以使用线段树来维护从而支持区间操作,单点修改同样迎刃而解了。

luogu P4751 【模板】动态 DP(加强版)

给定一个 \(n\) 个点的带点权树,进行 \(m\) 次修改点权的操作。

你需要在每次修改之后输出树上最大带权独立集的权值之和。

\(1\le n \le 10^6,1\le m\le 3\times10^6\)


老样子,先出来 DP 式子:

\[dp_{u,0}=\sum_{v\in son_u} \max(dp_{x,0},dp_{x,1}),dp_{u,1}=\sum_{v\in son_u} dp_{x,0} \]

非常好想。

这同样可以使用矩阵进行维护,吗?

由于操作都是在树上进行,一个点有可能从多个儿子节点合并,有没有办法只从一个节点合并并且能保证复杂度的做法呢?

有的,兄弟,有的:重链剖分

\(g_{u,0/1}\) 表示只考虑 \(u\) 的轻儿子的选/不选 \(u\) 的最大权独立集,\(dp_{u,0/1}\) 表示轻重儿子都考虑。

最后的答案就是 \(\max(dp_{1,0},dp_{1,1})\)

由于每个点到根节点所经过的轻边不超过 \(\log n\) 条,所以可以直接暴力 DP 转移所有的 \(g_{u,0/1}\)

\(son_u\) 表示 \(u\) 的重儿子,则矩阵如下:

\[\begin{bmatrix}dp_{son_u,0}&dp_{son_u,1}\\-\infty&-\infty\end{bmatrix}\times\begin{bmatrix}g_{u,0}&g_{u,1}\\g_{u,0}&-\infty\end{bmatrix}=\begin{bmatrix}dp_{u,0}&dp_{u,1}\\-\infty&-\infty\end{bmatrix} \]

(两个都开 \(2\times 2\) 只是个人习惯)

直接用树剖维护就行了,细节有点多,看代码吧:

PS:这是经过了加强版毒打后的卡常代码。

#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
vector<int> h[N];
int n, m, num_cnt;
int a[N];
int seg[N], rev[N], son[N], siz[N];
int fa[N], dep[N], top[N], low[N];
int g[N][2];
int rt[N], idx;
inline int read() {int x = 0, f = 1;char c = getchar();while (c < '0' || c > '9') {if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}return x * f;
}
struct mat {int a[2][2];mat() { memset(a, 0, sizeof a); }friend mat operator * (mat x, mat y) {mat z;for (int i = 0; i < 2; i ++ ) for (int j = 0; j < 2; j ++ ) {z.a[0][0] = max(x.a[0][0] + y.a[0][0], x.a[0][1] + y.a[1][0]);z.a[0][1] = max(x.a[0][0] + y.a[0][1], x.a[0][1] + y.a[1][1]);z.a[1][0] = max(x.a[1][0] + y.a[0][0], x.a[1][1] + y.a[1][0]);z.a[1][1] = max(x.a[1][0] + y.a[0][1], x.a[1][1] + y.a[1][1]);}return z;}
}hh;
static void dfs1(int u, int father) {fa[u] = father;dep[u] = dep[fa[u]] + 1;siz[u] = 1;for (int x : h[u]) if (x != fa[u]) {dfs1(x, u);siz[u] += siz[x];if (siz[x] > siz[son[u]]) son[u] = x;}
}
static void dfs2(int u) {if (son[u]) {top[son[u]] = top[u];seg[son[u]] = ++ num_cnt;rev[num_cnt] = son[u];dfs2(son[u]);}for (int x : h[u]) if (!top[x]) {top[x] = x;seg[x] = ++num_cnt;rev[num_cnt] = x;dfs2(x);}
}
struct TREE {int l, r;mat s;
}tr[N * 4];
inline static void pushup(int k) { tr[k].s = tr[tr[k].r].s * tr[tr[k].l].s; }
static void change(int &k, int l, int r, int p) {if (!k) k = ++idx;if (l == r) {mat &it = tr[k].s;it.a[0][0] = it.a[1][0] = g[rev[p]][0];it.a[0][1] = g[rev[p]][1];it.a[1][1] = -1e9;return ;}int mid = l + r >> 1;if (p <= mid) change(tr[k].l, l, mid, p);else change(tr[k].r, mid + 1, r, p);pushup(k);
}
static void build_tree(int u) {for (int x : h[u]) if (x != fa[u] && x != son[u]) {build_tree(x);mat it = hh * tr[rt[x]].s;g[u][0] += max(it.a[0][0], it.a[0][1]);g[u][1] += it.a[0][0];}if (son[u]) build_tree(son[u]);change(rt[top[u]], seg[top[u]], low[top[u]], seg[u]);
}
static void DO(int u) {int x = seg[top[u]], y = low[top[u]];mat it1 = hh * tr[rt[top[u]]].s;change(rt[top[u]], x, y, seg[u]);mat it2 = hh * tr[rt[top[u]]].s;int f = fa[top[u]];if (!f) return;g[f][0] -= max(it1.a[0][0], it1.a[0][1]);g[f][1] -= it1.a[0][0];g[f][0] += max(it2.a[0][0], it2.a[0][1]);g[f][1] += it2.a[0][0];DO(f);
}
int main() {ios::sync_with_stdio(0);cin.tie(0);n = read(), m = read();for (int i = 1; i <= n; i ++ ) a[i] = read(), g[i][1] = a[i];for (int i = 1; i < n; i ++ ) {int x = read(), y = read();h[x].push_back(y);h[y].push_back(x);}dfs1(1, 0);top[1] = rev[1] = seg[1] = num_cnt = 1;dfs2(1);for (int i = 1; i <= n; i ++ ) low[top[i]] = max(low[top[i]], seg[i]);build_tree(1);int la = 0;while (m -- ) {int x = read(), y = read();x ^= la;if (a[x] == y) {cout << la << '\n';continue;}g[x][1] -= a[x];a[x] = y;g[x][1] += y;DO(x);mat it = hh * tr[rt[1]].s;la = max(it.a[0][0], it.a[0][1]);cout << la << '\n';}return 0;
}

「CF573D」Bear and Cavalry

\(n\) 个人和 \(n\) 匹马,第 \(i\) 个人对应第 \(i\) 匹马。第 \(i\) 个人能力值 \(w_i\),第 \(i\) 匹马能力值 \(h_i\),第 \(i\) 个人骑第 \(j\) 匹马的总能力值为 \(w_i\times h_j\),整个军队的总能力值为 \(\sum w_i\times h_j\)(一个人只能骑一匹马,一匹马只能被一个人骑)。有一个要求:每个人都不能骑自己对应的马。让你制定骑马方案,使得整个军队的总能力值最大。

现在有 \(q\) 个操作,每次给出 \(a,b\),交换 \(a\)\(b\) 对应的马。每次操作后你都需要输出最大的总能力值。

\(2 \le n \le 30000,1\le q \le 10000\)


比较有意思的一道题。

想一想如果没有不能骑对应的马该怎么做?

显然是排序一遍直接乘。

现在考虑有需要对应的情况,我们依然从大到小排序后做(这里懒得讲了,直接引用一张网上找到的题解的图)

考虑如果有 \(4\) 个连续的,其实直接当作两个两个的就行,依次可以发现每一个 \(\ge 4\) 的都可以拆成 \(2/3\),并且肯定是最优的。

(事实上这篇题解讲漏了一些,比如理论上应该在 \(2/3\) 中取 \(\max\),因为可能会存在对应+不对应+对应的情况)

然后直接矩阵中维护 \(dp_i,dp_{i-1},dp_{i-2}\) 就行了,剩下有一点点细节,但是相对就十分简单了。

(吐槽:这玩意数据范围那么小,\(nq\) 暴力都能过)


感觉这个东西做多了就完全是套路了,不算难。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/897401.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

四款报表软件全解析:从山海鲸报表到Sisense的企业数据利器

概述 在大数据时代,企业对数据分析和决策支持的要求日益增强,报表软件已成为现代管理中不可或缺的重要工具。它们能够高效地整合、分析和展示数据,帮助企业从海量数据中快速提取有价值的信息,支持精准决策。本文将为大家介绍4款报表软件,这些软件各具特色,适用于不同规模…

day:16 银行项目转帐

一、手机转账 我主要负责了转账模块,这个大模块中包含了智能转账、手机转账、语音转账、预约转账、收款人管理等5个子模块,我这次着重介绍一下我们生活中使用最多的手机转账子模块。 对于转账需要关注的是转账前、转账中、转账后这三个状态下的测试。一个完整的业务流程就是用…

day:银行项目——理财业务

一、理财业务术语二、理财业务的分类三、购买流程理财客户签约风险评估理财产品的预约理财产品查询理财产品讲解 1、我最近做了一个银行项目,然后做了当时测试了当中的理财模块,我这边大概和您讲解一下 2、首先理财中有包括签约,风评,理财购买,赎回/撤销,以及理财查询,之…

空间遥感智能处理技术发展现状与趋势

在数字化时代,空间遥感技术已经成为获取地球表面信息的重要手段。随着卫星遥感技术的快速发展,获取的遥感数据量激增,这对遥感数据的智能处理提出了更高的要求。本文将探讨空间遥感智能处理技术的发展现状与未来趋势。 发展现状大数据与人工智能的融合:当前,遥感数据处理正…

day:16 银行项目讲解

一、熟悉银行业务 (1)核心业务:负债业务,公共业务,信贷业务,支付业务,核算业务,理财业务 手机银行 票据业务 企业网银业务 对公业务 柜台业务 (2)银行系统 核心系统(账务系统) 资管系统 客户端系统 服务端系统 数据计算平台 短信平台 语音系统 二、信贷业务 (1)e…

多智能体粒子环境(Multi-Agent Particle Env)食用指南--从入门到入土

0.项目地址:原地址:openai/multiagent-particle-envs: Code for a multi-agent particle environment used in the paper "Multi-Agent Actor-Critic for Mixed Cooperative-Competitive Environments" (github.com) 国内镜像:项目首页 - multiagent-particle-env…

Windows 提权-内核利用_1

本文通过 Google 翻译 Kernel Exploits Part 1 – Windows Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充。导航0 前言 1 旧版 Windows 系统内核利用 2 搜寻内核漏洞2.1 枚举内核利用 - 手动 2.2 枚举内核利用 - 自动…

探秘Transformer系列之(12)--- 多头自注意力

从零开始解析Transformer,目标是:(1) 解析Transformer如何运作,以及为何如此运作,让新同学可以入门;(2) 力争融入一些比较新的或者有特色的论文或者理念,让老鸟也可以有所收获。探秘Transformer系列之(12)--- 多头自注意力 目录探秘Transformer系列之(12)--- 多头自注…

设备与onenet服务器之间的mqtt通讯

设备与onenet服务器之间的通讯 1. 登录onenet平台2.创建产品3.设备管理 1)创建两个进行测试功能4.MQTTfx测试password计算工具官网下载:OneNET - 中国移动物联网开放平台1)订阅属性上报结果通知消息 Subscribe的topic: $sys/改成自己的产品ID/改成自己的设备名称/thing/pro…

设计一个优秀 API 的秘诀

本指南深入探讨了顶级 API 设计,强调了它不仅仅是代码的集合。一个设计良好的 API 就像五星级礼宾服务,能够顺畅地引导用户达到他们的预期目标。拿起一杯咖啡,让我们一起探索创建一个功能强大、用户友好的 API 的秘诀吧! 理解 API 基础第一步:拥抱 REST - API 设计的基础 …

T428497 请不要抢走爱丽丝的工作! 题解

T428497 请不要抢走爱丽丝的工作! 题解题目传送门 本题要求扫地机器人半径 \(r\) 的最大值,既然要求最大值,容易想到二分答案。 那问题就转换为给定半径 \(R\) , 判断 \(R\) 满不满足条件,也就是能不能找到一条绕过所有障碍的路径。让我们逆转一下思维,考虑哪些区域是机器…