CSP-J/S 复赛算法 线性DP

server/2024/10/9 1:25:43/

文章目录

  • 前言
  • 线性动态规划
  • DP算法三要素
  • 线性DP示例
    • 例题1:爬楼梯问题
      • 题目描述
      • 分析过程
      • 示例代码(C语言)
      • 例题2:最小路径和
        • 题目描述
        • 分析过程
        • 示例代码(C语言)
      • 例题3:最大子序和
        • 题目描述
        • 分析过程
        • 示例代码(C语言)
  • 总结


前言

算法竞赛中,动态规划(Dynamic Programming,简称DP)是一种强大的解题技巧,尤其在处理具有重叠子问题和最优子结构性质的问题时,表现得尤为出色。在线性动态规划中,我们关注的是通过线性结构(如数组或链表)来表达问题,并通过递推的方法来求解最优解。这类问题的特点在于,状态的转移通常只依赖于前一个或前几个状态,具有良好的可计算性。

在线性DP中,我们将问题划分为多个阶段,并在每个阶段进行状态更新。通过精心设计状态转移方程,我们能够逐步逼近问题的最优解。这种方法不仅可以显著降低时间复杂度,还能有效减少空间复杂度,使得解决复杂问题变得可行。


线性动态规划

线性动态规划其实是一种解决问题的小技巧,它的核心思想是“把大问题变成小问题,一步一步解决”。就像我们爬楼梯,如果要爬到楼顶,我们不可能一口气跳上去,而是需要一阶一阶地爬,每一步都建立在之前的基础上。线性动态规划也是这样,每一步的结果都要依赖前面几步已经算出来的结果,然后一点一点地推进到最终的答案。

简单来说,线性动态规划就是把复杂的问题拆成一系列简单的步骤,然后从简单到复杂,逐步解决。

DP算法三要素

动态规划(DP)算法中,状态阶段决策是非常重要的三个部分,可以把它们想象成解题过程中需要回答的三个关键问题。

  1. 状态:状态就像你在解题过程中的每一个“中间站”或“当前局面”。比如你在打游戏时,你当前的血量、分数、位置这些都可以称为“状态”。在DP算法中,每一个状态都会影响后面的选择和结果。

  2. 阶段:阶段可以理解为问题解决过程中的每一个“步骤”或“阶段”。就像你在做一个任务时,一步一步往前推进,每一阶段都有新的目标。在DP里,每一阶段我们都在解决一部分问题,直到最终完成所有阶段。

  3. 决策:决策就是每到一个阶段时你要做的“选择”。好比你在游戏里遇到敌人,你要选择是攻击还是防守。在DP算法中,每一阶段我们根据当前状态要做一个选择,这个选择会影响接下来的状态和结果。

通俗点讲,动态规划就是通过分析每个“状态”,在每个“阶段”做出“决策”,一步步积累,最终解决整个问题的过程。

线性DP示例

例题1:爬楼梯问题

题目描述

假设你在一个楼梯上,有 n 级台阶。每次你可以选择走 1 级或 2 级台阶。请问有多少种不同的方法可以到达楼顶?

分析过程

  1. 定义状态

    • 我们用 f(n) 表示到达第 n 级台阶的方法总数。
  2. 状态转移

    • 要到达第 n 级台阶,有两种可能:
      • 从第 n-1 级台阶走一步上来。
      • 从第 n-2 级台阶走两步上来。
    • 所以,我们可以得到公式:
      f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n) = f(n-1) + f(n-2) f(n)=f(n1)+f(n2)
    • 这就意味着,要到达第 n 级台阶的方法总数等于到达第 n-1 级台阶和 n-2 级台阶的方法总数之和。
  3. 初始条件

    • f(1) = 1:只有一种方法到达第一层(一步上去)。
    • f(2) = 2:有两种方法到达第二层(一步一步上去,或者一步跳两层)。
  4. 计算方法

    • 从小的台阶开始,利用我们得到的公式来计算到达更高台阶的方法数,直到 n 级台阶。

示例代码(C语言)

#include <stdio.h>int climbStairs(int n) {if (n == 1) return 1; // 1级台阶if (n == 2) return 2; // 2级台阶int a = 1; // f(1)int b = 2; // f(2)int result;for (int i = 3; i <= n; i++) {result = a + b; // f(n) = f(n-1) + f(n-2)a = b; // 更新 f(n-2)b = result; // 更新 f(n-1)}return result; // 返回 f(n)
}int main() {int n = 5; // 假设有5级台阶printf("到达 %d 级台阶的方法总数是:%d\n", n, climbStairs(n));return 0;
}

例题2:最小路径和

题目描述

在一个二维数组中,每个元素表示一个非负整数。你从左上角的格子开始,目标是到达右下角的格子。每次只能向下或向右移动,请问到达右下角的最小路径和是多少?

分析过程
  1. 定义状态

    • 我们用 dp[i][j] 表示到达位置 (i, j) 的最小路径和。
  2. 状态转移

    • 要到达位置 (i, j),我们只能从上面 (i-1, j) 或左边 (i, j-1) 移动过来。因此我们可以得到公式:
      d p [ i ] [ j ] = grid [ i ] [ j ] + min ⁡ ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j] = \text{grid}[i][j] + \min(dp[i-1][j], dp[i][j-1]) dp[i][j]=grid[i][j]+min(dp[i1][j],dp[i][j1])
    • 这意味着到达 (i, j) 的最小路径和等于当前位置的值加上从上面或左边到达的最小路径和。
  3. 初始条件

    • dp[0][0] = grid[0][0]:起始位置的路径和就是它本身。
    • 第一行和第一列的路径和只能从左或上方累计。
  4. 计算方法

    • 通过遍历整个二维数组,利用我们得到的公式来计算每个位置的最小路径和。
示例代码(C语言)
#include <stdio.h>#define MAX 100 // 假设数组的最大大小int minPathSum(int grid[MAX][MAX], int m, int n) {int dp[MAX][MAX];// 初始化起始点dp[0][0] = grid[0][0];// 初始化第一行for (int j = 1; j < n; j++) {dp[0][j] = dp[0][j-1] + grid[0][j];}// 初始化第一列for (int i = 1; i < m; i++) {dp[i][0] = dp[i-1][0] + grid[i][0];}// 填充整个dp数组for (int i = 1; i < m; i++) {for (int j = 1; j < n; j++) {dp[i][j] = grid[i][j] + (dp[i-1][j] < dp[i][j-1] ? dp[i-1][j] : dp[i][j-1]);}}return dp[m-1][n-1]; // 返回右下角的最小路径和
}int main() {int grid[3][3] = { {1, 3, 1},{1, 5, 1},{4, 2, 1} };printf("到达右下角的最小路径和是:%d\n", minPathSum(grid, 3, 3));return 0;
}

例题3:最大子序和

题目描述

给定一个整数数组,找出具有最大和的连续子数组(至少包含一个元素),返回其最大和。

分析过程
  1. 定义状态

    • dp[i] 表示以 nums[i] 结尾的最大子序和。
  2. 状态转移

    • 选择以 nums[i] 结尾的子序和,我们可以选择加上前面的子序和或重新开始。
    • 公式为:
      d p [ i ] = max ⁡ ( d p [ i − 1 ] + nums [ i ] , nums [ i ] ) dp[i] = \max(dp[i-1] + \text{nums}[i], \text{nums}[i]) dp[i]=max(dp[i1]+nums[i],nums[i])
    • 这意味着当前的最大子序和要么是前一个最大子序和加上当前元素,要么是当前元素本身。
  3. 初始条件

    • dp[0] = nums[0]:以第一个元素结尾的最大子序和就是它本身。
  4. 计算方法

    • 遍历整个数组,利用我们得到的公式来计算每个位置的最大子序和,并记录全局最大值。
示例代码(C语言)
#include <stdio.h>int maxSubArray(int* nums, int numsSize) {int dp[numsSize];dp[0] = nums[0];int max_sum = dp[0];for (int i = 1; i < numsSize; i++) {dp[i] = (dp[i-1] + nums[i] > nums[i]) ? (dp[i-1] + nums[i]) : nums[i];if (dp[i] > max_sum) {max_sum = dp[i]; // 更新全局最大值}}return max_sum; // 返回最大子序和
}int main() {int nums[] = {-2, 1, -3, 4, -1, 2, 1, -5, 4};int size = sizeof(nums) / sizeof(nums[0]);printf("最大子序和是:%d\n", maxSubArray(nums, size));return 0;
}

这两个例子展示了线性动态规划在解决实际问题中的应用。通过定义状态、状态转移和初始条件,我们能够有效地找到解决方案。


总结

线性动态规划为我们提供了一种系统化的方法来解决各种问题,尤其是那些涉及最优化的任务。通过明确的状态定义、合理的状态转移关系,以及有效的初始条件设置,我们能够高效地找到答案。在算法竞赛和实际编程中,掌握线性DP的基本思想和技巧是非常重要的。

无论是最小路径和、最大子序和,还是其他经典问题,线性DP都为我们提供了强大的解决工具。通过不断练习和总结,我们将能够更加熟练地应用这一方法,提升自己的编程能力和算法水平。


http://www.ppmy.cn/server/129023.html

相关文章

leetcode34. 在排序数组中查找元素的第一个和最后一个位置

原题链接&#xff1a;leetcode34 for循环查找 class Solution {public int[] searchRange(int[] nums, int target) {int a-1,b-1;for(int i0;i<nums.length;i){if(nums[i]target){ai;break;}}for(int jnums.length-1;j>0;j--){if(nums[j]target){bj;break;}}return ne…

C++ | Leetcode C++题解之第456题132模式

题目&#xff1a; 题解&#xff1a; class Solution { public:bool find132pattern(vector<int>& nums) {int n nums.size();vector<int> candidate_i {nums[0]};vector<int> candidate_j {nums[0]};for (int k 1; k < n; k) {auto it_i upper_…

Vue2 + ElementUI + axios + VueRouter入门

之前没有pc端开发基础&#xff0c;工作需要使用若依框架进行了一年的前端开发.最近看到一个视频框架一步步集成&#xff0c;感觉颇受启发&#xff0c;在此记录一下学习心得。视频链接:vue2element ui 快速入门 环境搭建和依赖安装 安装nodejs安装Vue Cli使用vue create proje…

论文翻译 | Model-tuning Via Prompts Makes NLP Models Adversarially Robust

摘要 近年来&#xff0c;NLP从业者集中于以下实践:(i)导入现成的预训练(掩码)语言模型;(ii)在CLS令牌的隐藏表示(随机初始化权重)上附加多层感知器;(iii)在下游任务(MLP-FT)上微调整个模型。这一过程在标准的NLP基准上产生了巨大的收益&#xff0c;但这些模型仍然很脆弱&#x…

若依从redis中获取用户列表

因为若依放入用户的时候&#xff0c;会在减值中添加随机串&#xff0c;所以用户的key会在redis中变成&#xff1a; login_tokens:6af07052-b76d-44dd-a296-1335af03b2a6 这样的样子。 如果用 Set<Object> items redisService.redisTemplate.keys("login_tokens&…

Docker_速通_01

Docker Docker笔记连接相关概念如下安装运行命令 命令镜像容器run细节根据容器制作新镜像对正在运行容器的修改,保存为镜像保存成文件加载文件成镜像 分享镜像登录修改名字 docker tag推送镜像 目录挂载卷映射创建卷 容器之间直接访问查看容器细节容器内部互相访问自定义网络创…

音视频入门基础:FLV专题(13)——FFmpeg源码中,解析任意Type值的SCRIPTDATAVALUE类型的实现

一、SCRIPTDATAVALUE类型 从《音视频入门基础&#xff1a;FLV专题&#xff08;9&#xff09;——Script Tag简介》中可以知道&#xff0c;根据《video_file_format_spec_v10_1.pdf》第80到81页&#xff0c;SCRIPTDATAVALUE类型由一个8位&#xff08;1字节&#xff09;的Type和…

【MySQL】Ubuntu环境下MySQL的安装与卸载

目录 1.MYSQL的安装 2.MySQL的登录 3.MYSQL的卸载 4.设置配置文件 1.MYSQL的安装 首先我们要看看我们环境里面有没有已经安装好的MySQL 我们发现是默认是没有的。 我们还可以通过下面这个命令来确认有没有mysql的安装包 首先我们得知道我们当前的系统版本是什么 lsb_…