动态规划(一)算法设计与分析 国科大

这篇文章并不是老师讲述的内容,主要是我DP学的太差了,所以补一篇来强化基础

首先先介绍一下动态规划:

和分治一样,动态规划也是将大问题拆分成小问题,不同的是,动态规划专门对付那些 "小问题会重复出现" 的情况,核心是 "把小问题的答案记下来,不用反复算,再用小问题的答案拼出大问题的答案"。而分治的子问题通常是独立不重复的。

下面看一个动态规划的例子

爬楼梯

假设你要爬 10 级楼梯,每次只能爬 1 级或 2 级,问有多少种不同的爬法?

  • 如果你用 "暴力试":想知道爬 10 级的方法,得先算爬 9 级和爬 8 级的方法(因为最后一步要么从 9 级爬 1 级,要么从 8 级爬 2 级);而算爬 9 级的方法,又得先算爬 8 级和 7 级的方法...... 这里 "爬 8 级的方法" 会被算两次(算 10 级时要算 8 级,算 9 级时也要算 8 级),反复计算会浪费很多时间。

  • 而用动态规划:

    1. 先算 "小问题" 的答案:
      • 爬 1 级:只有 1 种方法(直接爬 1 级),记下来:dp[1]=1
      • 爬 2 级:有 2 种方法(1+1,或直接爬 2 级),记下来:dp[2]=2
    2. 用小问题的答案拼大问题:
      • 爬 3 级 = 爬 2 级的方法 + 爬 1 级的方法 → dp[3]=dp[2]+dp[1]=3
      • 爬 4 级 = 爬 3 级的方法 + 爬 2 级的方法 → dp[4]=dp[3]+dp[2]=5
      • ...... 以此类推,直到算出dp[10]

可以看到,我们通过利用之前求解过的子问题,极大减少了计算步骤。(这个例子和求解斐波那契数列几乎一致)

再看一个例子:

凑零钱

要凑出 10 块钱,只有 1 块、2 块、5 块的硬币,问最少需要几个硬币?

  • 动态规划的思路是:
    1. 先算 "凑出小金额" 的最少硬币数:
      • 凑 1 块:最少 1 个(1 块),记dp[1]=1
      • 凑 2 块:最少 1 个(2 块),记dp[2]=1
      • 凑 3 块:要么 "凑 2 块 + 1 块"(1+1=2 个),要么 "凑 1 块 + 2 块"(1+1=2 个),记dp[3]=2
    2. 凑 10 块的最少硬币数 = 以下三种情况的最小值:
      • 凑 9 块的最少硬币数 + 1 个 1 块 → dp[9]+1
      • 凑 8 块的最少硬币数 + 1 个 2 块 → dp[8]+1
      • 凑 5 块的最少硬币数 + 1 个 5 块 → dp[5]+1

同样的,也是利用已知的小问题来拼接成我们要求的大问题

总结一下动态规划的特点:

  1. 能拆:大问题可以拆成多个小问题;
  2. 重复:小问题会被反复用到,所以记下来省时间;
  3. 能拼:大问题的答案,能从 "小问题的答案" 里组合出来。

一般来说,我们会用一个数据结构来记录重复的子问题。(本文中涉及的问题都比较基础,因此都用数组dp[n]来进行记录,求解动态规划问题的核心,也就是求解dp[n])

下面看一个爬楼梯的进阶版

现在一次最多可以爬上的台阶从2变为了k,但是逻辑是一样的。

当处于第i个台阶时,共有k种情况可以爬到:

从第i-k阶直接爬k个台阶;

从第i-k+1阶直接爬k+1个台阶

从第i-k+2阶直接爬k-2个台阶

..............

总结一下,要到达第 i 级台阶,最后一步的步数只能是 1~K 中的某一个(假设最后一步迈了 j 级,j∈[1,K]),因此:到达第 i 级的方式数 = 所有 "到达第 i-j 级的方式数" 之和(j 从 1 到 K,但需满足 i-j ≥ 0,否则 i-j 不存在)

因此,我们可以尝试写出一种计算dp[n](假设K=3):

dp[1],即爬上第1个台阶有几种方式,显然只有1种方式,从最底(dp[0])一次爬1个台阶,因此dp[1]=dp[0]=1;

dp[2],即爬上第2个台阶有几种方式,可以从最底(dp[0])一次爬两个台阶,也可以从第1个台阶(dp[1])再爬一个台阶,因此dp[2]=dp[1]+dp[0]=2;

dp[3],即爬上第3个台阶有几种方式,可以从最底(dp[0])一次爬3个台阶,也可以从第1个台阶(dp[1])再爬两个台阶,还可以从第2个台阶(dp[2])爬一个台阶,因此dp[3]=dp[2]+dp[1]+dp[0]=4;

dp[4],即爬上第4个台阶有几种方式,可以从第1个台阶(dp[1])一次爬3个台阶,也可以从第2个台阶(dp[2])再爬两个台阶,还可以从第3个台阶(dp[3])爬一个台阶,因此dp[4]=dp[3]+dp[2]+dp[1]=7;

dp[5],即爬上第5个台阶有几种方式,可以从第2个台阶(dp[2])一次爬3个台阶,也可以从第3个台阶(dp[3])再爬两个台阶,还可以从第4个台阶(dp[4])爬一个台阶,因此dp[5]=dp[4]+dp[3]+dp[2]=13;

............

dp[n],即爬上第n个台阶有几种方式,可以从第n-3个台阶(dp[n-3])一次爬3个台阶,也可以从第n-2个台阶(dp[n-2])再爬两个台阶,还可以从第n-1个台阶(dp[n-1])爬一个台阶,因此dp[n]=dp[n-3]+dp[n-2]+dp[n-1];


上例中K=3,那么能直接写出K=4时候的dp[n]吗?大家可以尝试一下:

dp[n]=dp[n-4]+dp[n-3]+dp[n-2]+dp[n-1];

同样的,K=5、K=6也很容易写出,我们把这样的等式称为状态转移方程。

到达第 i 级的方式数 = 所有 "到达第 i-j 级的方式数" 之和(j 从 1 到 K,但需满足 i-j ≥ 0,否则 i-j 不存在)

由此可以通过双循环,外部循环控制数组dp的值,内部循环j 从 1 到 K来计算。

代码如下:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

int n, k, dp[1000000];
int ans = 100003;

int main()
{
    cin >> n >> k;
    dp[0] = 1;
    dp[1] = 1;
    for(int i = 2; i <= n; i++)
    {
        for(int j = 1; j <= k; j++)
        {
            if(i >= j)
            dp[i] = (dp[i] + dp[i - j]) % ans;
        }
    }
    cout << dp[n] % ans;
}

下一篇文章仍会有两个较为简单的例子来理解DP。

相关推荐
过河卒_zh15667661 天前
喜讯:第十五批生成合成类算法备案备案号公布
人工智能·算法·aigc·生成式人工智能·算法备案
cpp_25011 天前
B3927 [GESP202312 四级] 小杨的字典
数据结构·c++·算法·题解·洛谷
Cx330❀1 天前
《C++ 递归、搜索与回溯》第2-3题:合并两个有序链表,反转链表
开发语言·数据结构·c++·算法·链表·面试
AI科技星1 天前
电磁耦合常数Z‘的第一性原理推导与严格验证:张祥前统一场论的几何基石
服务器·人工智能·线性代数·算法·矩阵
AI科技星1 天前
电场起源的几何革命:变化的引力场产生电场方程的第一性原理推导、验证与统一性意义
开发语言·人工智能·线性代数·算法·机器学习·数学建模
中國龍在廣州1 天前
“物理AI”吹响号角
大数据·人工智能·深度学习·算法·机器人·机器人学习
꧁Q༒ོγ꧂1 天前
算法详解(二)--算法思想基础
java·数据结构·算法
꧁Q༒ོγ꧂1 天前
算法详解(一)--算法系列开篇:什么是算法?
开发语言·c++·算法
橘颂TA1 天前
【剑斩OFFER】算法的暴力美学——力扣:1047 题:删除字符串中的所有相邻重复项
c++·算法·leetcode·职场和发展·结构于算法
2301_800256111 天前
R-Tree创建与遍历,R-Tree在4类空间查询中的应用,实现4类空间查询的各类算法[第8章]
数据库·算法·机器学习·postgresql·r-tree