【算法/训练】:动态规划(线性DP)

一、路径类

1. 字母收集

思路:

1、预处理

     对输入的字符矩阵我们按照要求将其转换为数字分数由于只能往下和往右走,因此走到(i,j)的位置要就是从(i - 1, j)往下走,或者是从(i,j  - 1)的位置往右走,由于我们要使得路程遍历积分最多,则应该从积分多的位置过来,

2、状态表示 dp[i][j] 表示:从[0, 0]出发,到底[i, j]位置,一共有多少分

3、状态转移方程

    故(i,j)位置的积分应该为dp[ i ][ j ] = max(dp[ i - 1 ][ j ], dp[ i ][ j - 1 ]) + dp[ i ][ j ];

4、初始化

    但是上面仅对于(i >= 1 && j >= 1)成立,对于第一行和第一列我们应该特殊处理,利用前缀和的知识可以求得,走到第一列的第i个位置最多能拿的积分以及走到第一行的第j个位置最多能拿的积分,然后我们就可以按照dp[ i ][ j ] = max(dp[ i - 1 ][ j ], dp[ i ][ j - 1 ]) + dp[ i ][ j ]的方法遍历每个节点即可

AC代码如下:

#include <iostream>
using namespace std;const int N = 1005;
int dp[N][N];int main() {int n, m;cin >> n >> m;char ch;for (int i = 0; i < n; i++){for (int j = 0; j < m; j++){cin >> ch;if (ch == 'l') dp[i][j] = 4;else if (ch == 'o') dp[i][j] = 3;else if (ch == 'v') dp[i][j] = 2;else if (ch == 'e') dp[i][j] = 1;else a[i][j] = 0;}}for (int i = 1; i < n; i++) dp[i][0] = dp[i - 1][0] + dp[i][0]; 
for (int j = 1; j < m; j++) dp[0][j] = dp[0][j - 1] + dp[0][j]; for (int i = 1; i < n; i++){for (int j = 1; j < m; j++){dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + dp[i][j];}}cout << dp[n - 1][m - 1] << endl;return 0;
}

2、[NOIP2002 普及组] 过河卒

分析:

思路:
1、状态表示
dp[i][j] 表示:从[0, 0]出发,到底[i, j]位置,一共有多少种方法
2、状态转移方程

    dp[ i ][ j ] = dp[ i - 1 ][ j ] + dp[i][j - 1] (i > 0 && j > 0)

当走到马可以走的地方,dp[ i ][ j ] = 0;
3、初始化

先创建一个 dp[ n + 2 ][ m + 2 ],然后让dp[ 0 ][ 1 ] = 1 或者 dp[ 1 ][ 0 ] = 1。注意这样初始化的时候,x需要+1,y也需要+1.和dp表位置一一对应

#include <iostream>
#include <vector>
using namespace std;//int dp[1005][1005];
int main()
{int n, m, x, y;cin >> n >> m >> x >> y;vector<vector<long long>> dp(n + 2, vector<long long>(m + 2));dp[0][1] = 1;x += 1, y += 1;和dp表位置一一对应for (int i = 1; i <= n + 1; i++){for (int j = 1; j <= m + 1; j++) { //马所在位置if (i != x && j != y && abs(i - x) + abs(j - y) == 3 || (i == x && j == y)){dp[i][j] == 0;}else dp[i][j] = dp[i - 1][j] + dp[i][j - 1];}}cout << dp[n + 1][m + 1] << endl;return 0;
}

二、子序列和连续序列类

1. mari和shiny

🌈线性 dp

在维护 i 位置之前,⼀共有多少个 "s" "sh" ,然后更新 "shy" 的个数。

(1)状态表示

  • s[i]:字符串 str 中 [0, i] 区间内有多少个 "s"。

  • h[i]:字符串 str 中 [0, i] 区间内有多少个 "sh"。

  • y[i]:字符串 str 中 [0, i] 区间内有多少个 "shy。


(2)状态转移方程


(3)空间优化

用三个变量来表示即可

s:(字符串 str 中 [0, n-1] 区间内有多少个 "s")

h:(字符串 str 中 [0, n-1] 区间内有多少个 "sh")

y:(字符串 str 中 [0, n-1] 区间内有多少个 "shy")

最后的遍历结束后,y即我们需要的结果

AC代码如下:

#include <iostream>
#include <string>
using namespace std;
typedef long long ll;
int main()
{int n;string str;cin >> n >> str;ll s = 0, h = 0, y = 0;for (int i = 0; i < n; i++) {if (str[i] == 's') s++;else if (str[i] == 'h') h += s;else if (str[i] == 'y') y += h;}cout << y << endl;return 0;
}
🌈二维 dp 

这道题目如果不是子序列,而是要求连续序列的,那就可以考虑用 KMP。

(1)dp[i][j] 含义

  • string t="shy"

dp[i][j]:以 i-1 为结尾的 str 子序列中出现以 j-1 为结尾的 t 的个数为 dp[i][j]。


(2)递推关系

  • str[i - 1] 与 t[j - 1]相等
  • str[i - 1] 与 t[j - 1] 不相等

当 str[i - 1] 与 t[j - 1]相等时,dp[i][j] 可以有两部分组成:

  1. 一部分是用 str[i - 1] 来匹配,那么个数为 dp[i - 1][j - 1]。即不需要考虑当前 str 子串和 t 子串的最后一位字母,所以只需要 dp[i-1][j-1]。
  2. 一部分是不用 str[i - 1] 来匹配,个数为 dp[i - 1][j]。

为什么还要考虑不用 str[i - 1] 来匹配,都相同了指定要匹配?
🧩例如: str:shyy 和 t:shy ,str[3] 和 t[2] 是相同的,但是字符串 str 也可以不用 str[3] 来匹配,即用 str[0]str[1]str[2] 组成的 "shy"。当然也可以用 str[3] 来匹配,即:str[0]str[1]str[3] 组成的 "shy"。

所以,当 str[i - 1] 与 t[j - 1] 相等时,dp[ i ][ j ] = dp[ i - 1 ][ j - 1 ] + dp[ i - 1 ][ j ];

当 str[i - 1] 与 t[j - 1] 不相等时,dp[i][j] 只有一部分组成,不用 str[i - 1] 来匹配(就是模拟在 str 中删除这个元素),即:dp[i - 1][j],所以递推公式为:dp[ i ][ j ] = dp[ i - 1 ][ j ];

为什么只考虑 “不用 str[i - 1] 来匹配” 这种情况, 不考虑 “不用 t[j - 1] 来匹配” 的情况呢?
🧩这里要明确,我们求的是 str 中有多少个 t,而不是求 t 中有多少个 str,所以只考虑 str 中删除元素的情况,即不用 str[i - 1] 来匹配 的情况。


(3)状态转移方程

  • dp[i][j]显然要从dp[i-1][?]递推而来。立即思考dp[i-1][j], dp[i-1][j-1]分别与dp[i][j]的关系。因为少一个字符,自然而然从当前字符着手。考察si的第i个字符(表为s[i])和tj的第j个字符(表为t[j])的关系。

  • 若s[i] ≠ t[j]:那么s_i中的所有t_j子序列,必不包含s[i],即s_i-1和s_i中tj的数量是一样的,得到该情形的转移方程: dp[ i ][ j ] = dp[ i -1 ][ j ]

  • 若s[i] = t[j]:假设s_i中的所有t_j子序列中,包含s[i]的有a个,不包含的有b个。s_i中包含s[i]的子序列个数相当于s_i-1中t_j-1的个数,不包含s[i]的子序列个数与上一种情况一样,于是得到该情形的转移方程:

    a = dp[ i -1 ][ j -1 ] b = dp[ i-1 ][ j ] dp[ i ][ j ] = a + b = dp[i-1][j-1] + dp[i-1][j]


(4)遍历顺序

从上到下,从左到右。

AC代码如下:

#include <iostream>
#include <vector>using namespace std;int main()
{int n;cin >> n;string str;cin >> str;string t="shy";int m=t.size();vector<vector<long long>> dp(n+1, vector<long long>(m+1));for(int i=0; i<=n; i++) dp[i][0]=1;for(int i=1; i<=n; i++){for(int j=1; j<=m; j++){if(str[i-1]==t[j-1])dp[i][j]=dp[i-1][j-1]+dp[i-1][j];elsedp[i][j]=dp[i-1][j];}}cout << dp[n][m] << endl;return 0;
}

2. 不同的子序列

该题于上题不一样的是,上面给定了t的具体字符串,而这里没有给定,但是我们也需要用二维dp的方法来写。

(1)dp[i][j] 含义

s[ i ]的子序列中在t[ j ]出现的次数

s[ i ]表示s从下标 i 到末尾的子字符串。

t[ j ]表示t从下标 j 到末尾的子字符串。


(2)递推关系

  1. 分别令两个维度为0,推测边界。
  2. dp[0][j]表示s_0中t_j的个数。s_0是空字符串,只有当j=0时,才有dp[0][j] = 1,表示s子空串中有一个t子空串,否则dp[0][j] = 0,因为一个空串不可能包含一个非空串。
  3. dp[i][0]表示s_i中t0的个数。t_0是空字符串,显然任何串(包括空串)都含有一个空子串。因此dp[i][0] = 1。

  4. 注意到,dp[i][0] = 1实际上已经包含了dp[0][j] = 1的情形。


(3)初始化

  • dp[i][0] 表示:以 i-1 为结尾的 str 可以随便删除元素,出现空字符串的个数。所以,dp[i][0] 一定都是 1,因为也就是把以 i-1 为结尾的 str,删除所有元素,出现空字符串的个数就是 1。
  • dp[0][j] 表示:空字符串 str 可以随便删除元素,出现以 j-1 为结尾的字符串 t 的个数。所以,dp[0][j] 一定都是 0,因为 str 如论如何也变成不了 t。
  • dp[0][0] 表示:空字符串 str 可以删除 0 个元素,变成空字符串 t。所以,dp[0][0] = 1。

(4)遍历顺序

从上到下,从左到右。

​
int numDistinct(string s, string t) {int n = s.size(), m = t.size();if (n < m) return 0;vector<vector<unsigned int>> dp(n + 1, vector<unsigned int>(m + 1)); //注意是unsigned intfor (int i = 0; i <= n; i++) dp[i][0] = 1;for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {dp[i][j] = dp[i - 1][j] +(s[i - 1] == t[j - 1] ? dp[i - 1][j - 1] : 0);}}return dp[n][m];
}​

三、偷盗问题

1. 打家劫舍

思路:

首先考虑最简单的情况。如果只有一间房屋,则偷窃该房屋,可以偷窃到最高总金额。如果只有两间房屋,则由于两间房屋相邻,不能同时偷窃,只能偷窃其中的一间房屋,因此选择其中金额较高的房屋进行偷窃,可以偷窃到最高总金额。

如果房屋数量大于两间,应该如何计算能够偷窃到的最高总金额呢?对于 k( k > 2)的房屋数量,有两个选项:

  • 偷窃第k间房屋,那么就不能偷窃第 k−1 间房屋,偷窃总金额为前 k−2 间房屋的最高总金额与第 k 间房屋的金额之和。
  • 不偷窃第 k 间房屋,偷窃总金额为前 k−1 间房屋的最高总金额。

    在两个选项中选择偷窃总金额较大的选项,该选项对应的偷窃总金额即为前 k 间房屋能偷窃到的最高总金额。

1、状态表示
dp[ i ]:表示前i间房屋能偷盗的最多钱数。
2、状态转移方程

    dp[ i ] = max(dp[ i - 2 ] + nums[ i ] , dp[i - 1] )

提示里说了nums.length>=1;

对于nums.length==1,需特殊处理,return nums[0];

AC代码如下:

int rob(vector<int>& nums) {int n = nums.size();//方法一/*if (n == 1) return nums[0];vector<int> dp(n + 1);dp[0] = nums[0];dp[1] = max(nums[0], nums[1]);for (int i = 2; i < n; ++i) {dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);}return dp[n - 1];*///方法二int f1 = 0, f0 = 0;for (int i = 0; i < n; i++) {int tmp = max(f1, f0 + nums[i]);f0 = f1;f1 = tmp;}return f1;
}

2. 打家劫舍 II

思路:

    这道题中的房屋是首尾相连的,第一间房屋和最后一间房屋相邻,因此第一间房屋和最后一间房屋不能在同一晚上偷窃。和上题相似,这道题也可以使用动态规划解决。

考虑是否偷 nums[0]:

  • 如果偷 nums[0],那么 nums[1] 和 nums[n−1] 不能偷,问题变成从 nums[2] 到 nums[n−2] 的非环形版本。
  • 如果不偷 nums[0],那么问题变成从 nums[1] 到 nums[n−1] 的非环形版本。
  • 分别取 [start,end)=[2,n−2) 和 [start,end)=[1,n−1) 进行计算,取两个 dp[end] 中的最大值,即可得到最终结果

假设偷窃房屋的下标范围是 [start,end],用 dp[i] 表示在下标范围 [start,i] 内可以偷窃到的最高总金额:

1、状态表示
   dp[ i ]:表示前i间房屋能偷盗的最多钱数。
2、状态转移方程

    dp[i]=max(dp[i−2]+nums[i],dp[i−1])

注意:这题我们用 tmp, f1, f0 来分别表示dp[i],dp[i - 1],dp[i -2]。 

int robRange(vector<int>& nums, int start, int end) { // [start, end) 左闭右开int f1 = 0 , f0 = 0;for (int i = start; i < end; i++) {int tmp = max(f1, f0 + nums[i]);f0 = f1;f1 = tmp;}return f1;
}int rob(vector<int>& nums) {int n = nums.size();return max(nums[0] + robRange(nums, 2, n - 1), robRange(nums, 1, n));
}

3. 打家劫舍 III

思路:

对于当前节点,就两个选择,或者放弃然后我们用 f(o) 表示抢 o 节点的情况下,o 节点的子树上被选择的节点的最大权值和;g(o) 表示不抢 o 节点的情况下,o 节点的子树上被选择的节点的最大权值和;l 和 r 代表 o 的左右孩子。

🧩1、当 o 被选中时,o 的左右孩子都不能被选中,故 o 被选中情况下子树上被选中点的最大权值和为 l 和 r 不被选中的最大权值和相加,即 f(o)=g(l)+g(r)
🧩2、当 o 不被选中时,o 的左右孩子可以被选中,也可以不被选中。对于 o 的某个具体的孩子 x,它对 o 的贡献是 x 被选中和不被选中情况下权值和的较大值。故 g(o)=max{f(l),g(l)}+max{f(r),g(r)}
🧩至此,我们可以用哈希表来存 f 和 g 的函数值,用深度优先搜索的办法后序遍历这棵二叉树,我们就可以得到每一个节点的 f 和 g。根节点的 f 和 g 的最大值就是我们要找的答案。

unordered_map<TreeNode*, int>f, g;
void dfs(TreeNode* node)
{if (node == nullptr) return;dfs(node->left), dfs(node->right);f[node] = node->val + g[node->left] + g[node->right]; //如果抢该节点g[node] = max(f[node->left], g[node->left]) + max(f[node->right], g[node->right]);
}int rob(TreeNode* root) {dfs(root);return max(f[root], g[root]);
}

4. 打家劫舍 IV

思路:

     通过观察可以发现,当偷盗能力越大时,小偷最多能偷的房屋数就越多;如果偷盗能力越小时,小偷最多能偷的房屋数就越少。

     由题意我们可知,这是求最小的最大值,因此我们可以想到用二分的方法来找到合适的 capacity。对于每个通过二分列举出来的 capacity,因为碍于偷盗能力,小偷只能偷价值不超过 capacity 的房子,而且不能连续偷。因此,我们可以通过动态规划的方法算出:当小偷的偷盗能力为 capacity 时,小偷可以最多可以偷多少间房(设为 y),如果 y >= k,那么就代表 capacity 偏大,要把 capacity 调小一点;如果 y < k,那么就代表 capacity 偏小,把 capacity 调大一点。且由于要求的是最小的最大值,因此属于二分里面的区间左端点问题。

处。

     通过二分枚举 capacity,对每个 capacity 进行动态规划,求出在该 capacity 的情况下最多偷到的房屋数,然后再根据这个房屋数调整 capacity 的查找区间。

int minCapability(vector<int>& nums, int k) {vector<int> capacity = nums;sort(capacity.begin(), capacity.end());int l = 0, r = capacity.size() - 1;while (l < r){int m = (r - l >> 1) + l; //注意:+, -的优先级高于>>if (check(nums, capacity[m]) >= k)r = m;else l = m + 1;}return capacity[l];
}int check(vector<int>& nums, int capacity)
{int n = nums.size();vector<vector<int>> dp(n + 1, vector<int>(2));for (int i = 1; i <= n; i++){if (nums[i - 1] <= capacity) dp[i][1] = dp[i - 1][0] + 1;dp[i][0] = max(dp[i - 1][1], dp[i - 1][0]);}return max(dp[n][1], dp[n][0]);
}

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

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

相关文章

vector清空

https://www.zhihu.com/question/592055868/answer/2967078686

java使用hutool工具检查远程端口是否开启

使用java校验ip地址或域名的端口是否开启 1.导入hutool工具的maven依赖 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version></dependency>2.复制一下代码案例直接运行 …

前端面试基础题(微信公众号:前端面试成长之路)

BFC、IFC、GFC、FFC CSS2.1中只有BFC和IFC, CSS3中才有GFC和FFC。 到底什么是BFC、IFC、GFC和FFC Whats FC&#xff1f; 一定不是KFC&#xff0c;FC的全称是&#xff1a;Formatting Contexts&#xff0c;是W3C CSS2.1规范中的一个概念。它是页面中的一块渲染区域&#xff0c;并…

量度卓越:Mojo模型中自定义评估与模型比较的艺术

量度卓越&#xff1a;Mojo模型中自定义评估与模型比较的艺术 在机器学习项目中&#xff0c;模型评估是衡量算法性能的关键步骤。Mojo模型&#xff0c;作为一个先进的机器学习框架&#xff0c;提供了丰富的工具来支持模型评估和比较。本文将深入探讨如何在Mojo模型中实现自定义…

openj9-17.0.2_8-jre-alpine 和 openjdk:17-alpine 的区别是什么?

openj9-17.0.2_8-jre-alpine 和 openjdk:17-alpine 都是用于运行 Java 应用程序的 Docker 镜像&#xff0c;但它们之间有一些关键的区别&#xff1a; JVM Implementation: openj9-17.0.2_8-jre-alpine 使用的是 Eclipse OpenJ9&#xff0c;这是一种高效、低内存消耗的 JVM 实现…

go-sql-driver/mysql 查询 latin1 中文字符集

select name from table; table是 latin1 编码&#xff0c; 返回后查询结果后&#xff0c;即使将 name 转为 utf-8&#xff0c;日志输出中文仍然乱码。 // 配置数据库连接字符串&#xff0c;确保指定charsetlatin1dsn : "user:passwordtcp(127.0.0.1:3306)/dbname?chars…

免费【2024】springboot 宠物领养救助平台的开发与设计

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

每日一练,java07

目录 题目1.请问运行主要的程序会打印出的是什么&#xff08;&#xff09;2.下面论述正确的是&#xff08;&#xff09;&#xff1f;3.下面哪些Java中的流对象是字节流?4.关于以下代码的说明&#xff0c;正确的是&#xff08; &#xff09;5.若需要定义一个类&#xff0c;下列…

普元EOS学习笔记-EOS项目HTTP访问安全和权限控制

前言 对于企业应用系统&#xff0c;出于安全和权限控制的目的&#xff0c;需要对http请求做若干控制。 比如文件上传的时候要控制不允许上传的文件后缀。 又比如控制应用程序中的哪些资源不允许被访问。 EOS项目通过 xml配置文件来实现这一需求。 Http访问管理模块 在EOS项…

Keepalived、MyCAT 和 MHA这三者之间的区别

最近公司要做主备数据库备份和自动切换&#xff0c;先简单的了解了一下这三者的区别。 要做一个主库宕机之后自动切换备库的功能&#xff0c;但是上网搜索了一下发现有三个中间件都可以解决这个问题。 所以就来说一下这三个的业务场景和区别&#xff0c;以及哪一个更加轻量级 …

Spring JPA不生效

今天排查老半天发现数据源被排除装配了&#xff0c;把这个去掉就行了 SpringBootApplication(exclude DataSourceAutoConfiguration.class)

vue项目的路由如何传参,应用场景

路由传参方法一&#xff1a; params 1. 在路由的配置中 path: url:proid 先在配置中添加 2. 跳转页面的时候携带拼接参数 router.push(url proid) 3. 在进入的页面通过 useRoute 进行接收 4. const route useRoute() 5. console.log(route.params.proid); 路由传参方法二…

DBoW3相关优化脉络

1 DBow3 GitHub - rmsalinas/DBow3: Improved version of DBow2 2 优化后得到fbow GitHub - rmsalinas/fbow: FBOW (Fast Bag of Words) is an extremmely optimized version of the DBow2/DBow3 libraries. 其中fbow是ucoslam的一部分&#xff1b; ucoslam GitHub - la…

【C++版本】protobuf与gRPC

文章目录 一、Protobuf二、安装以及使用protoc参考 一、Protobuf Google Protocol Buffers&#xff08;protobuf&#xff09;是一种语言中立、平台中立的序列化协议&#xff0c;旨在高效地将结构化数据进行序列化和反序列化。它主要用于通信协议、数据存储和其他需要高效编码和…

封装和桥接Unity 协程体系

简介 协程&#xff08;Coroutine&#xff09;在C#中是一种特殊的函数&#xff0c;它允许开发者编写可以暂停执行并在未来某个时刻恢复执行的代码块。协程通常用于实现异步操作&#xff0c;如延时执行、等待某个事件发生、或者分段执行复杂的任务。在Unity游戏引擎中&#xff0c…

(Arxiv-2023)MobileDiffusion:移动设备上即时文本到图像生成

MobileDiffusion&#xff1a;移动设备上即时文本到图像生成 Paper Title&#xff1a;MobileDiffusion: Instant Text-to-Image Generation on Mobile Devices Paper是谷歌出品 Paper地址 图 1&#xff1a;MobileDiffusion 用于 (a) 文本到图像的生成。(b) Canny 边缘到图像、风…

docker容器cuda不可用,怎么解决?

通过Docker 构建的镜像中,启动之后,发现容器内部读取不到显卡驱动nvidia-smi 1、设置 NVIDIA Docker 存储库 distribution=$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add

Docker容器基础篇

一.Docker容器简要介绍 Docker 是一个开源项目&#xff0c;旨在提供轻量级的应用容器化解决方案。它允许开发者打包应用及其所有依赖项到一个标准化的单元中&#xff0c;称为容器。这些容器可以在开发人员的工作环境中构建&#xff0c;然后轻松地在不同的计算机、服务器或云平…

Linux更换pip下载镜像源

Windows中修改pip下载源-CSDN博客 本机Linux发行版本: Rocky_linux9.4 方法一&#xff1a; [rootlocalhost ~]# mkdir /root/.pip [rootlocalhost ~]# vim /root/./.pip/pip.conf [global] index-url http://mirrors.aliyun.com/pypi/simple/[install] trusted-hostmirror…

Laravel Mix与Webpack集成:打造现代化前端开发流程

Laravel Mix与Webpack集成&#xff1a;打造现代化前端开发流程 Laravel不仅仅是一个后端框架&#xff0c;它还为前端开发提供了强大的工具。Laravel Mix是一个强大的前端集成工具&#xff0c;它使用Webpack作为底层&#xff0c;为Laravel项目提供了一系列编译和热重载功能。本…