动态规划入门

本文介绍了动态规划的基本概念,包括子问题与原问题的关系、状态和状态转移方程的定义,以及动态规划问题的一般解题步骤。通过洛谷P1164问题实例,详细展示了如何应用这些概念求解实际问题。
摘要由CSDN通过智能技术生成

一、什么是动态规划?

动态规划(Dynamic Programming),简称DP,是运筹学的一个分支,通常用来解决多阶段决策过程最优化问题。动态规划的基本思想就是将原问题转换为一系列相互联系的子问题,然后通过逐层递推来求得最后的解。


二、动态规划的相关概念

(1)子问题和原问题

原问题就是你要求解的这个问题本身,子问题是和原问题相似但规模较小的问题(原问题本身就是子问题的最复杂的情形,即子问题的特例)。

例如:要求出F(10),那么求出F(10)就是原问题,求出F(k)(k<=10)都是子问题。

(2)状态

状态就是子问题中会变化的某个量,可以把状态看成我们要求解的问题的自变量。

例如:我们要求的F(10),那么这里的自变量10就是一个状态。

(3)状态转移方程

能够表示状态之间转移关系的方程,一般利用关于状态的某个函数建立起来。

例如:F(n) = F(n - 1) + F(n - 2),当n>2且n为整数时;当n = 1或 2 时,F(n) = 1,这种最简单的初始条件一般称为边界条件,也被称为基本方程。

(4)DP数组

DP数组也可以叫“子问题数组”,因为DP数组中的每一个元素都对应一个子问题的结果,DP数组的下标一般就是该子问题对应的状态。


根据上面的概念,我们可以明确一点:动态规划中的每一个状态一定是由上一个状态推导出来的,这一点就区别于贪心算法,贪心算法一般没有状态推导,而是直接从局部选最优。


三、动态规划问题的一般解题步骤

1.确定DP数组以及其下标的含义

2.确定状态转移方程

3.确定DP数组最简单的情况(DP数组的初始化)

4.确定数组的遍历顺序

5.举例推导DP数组


例题如下

题目来源:洛谷P1164


题目信息可化简如下:拥有金钱数为M,现有N种菜品,每种菜品有自己的价格,求有几种选择菜品的方案能够刚好花光金钱M。

首先我们明确,当N == 1且M == 该菜品的价格时,方案数就只有一个;若M != 菜品的价格,那么就没有成立的方案。因此,我们需要根据给定的M和当前菜品的价格进行讨论,我们就可以定义dp数组dp[i][j]。首先明确dp数组的含义,dp[i][j]表示用 j 元吃前 i 种菜,然后我们再定义数组a[i]来存放每道菜的价格。

根据以上分析,我们还需要确定状态转移方程。关于如何确定状态转移方程,需要根据给定的M和当前菜品的价格进行讨论。容易得到以下情况:

(1)若拥有的钱M与第 i 种菜价格相同,方案数为用M吃前 i - 1 种菜的方案数 + 用M只吃第 i 种菜

即:dp[i][j] = dp[i - 1][j] + 1;

(2)若拥有的钱M大于第 i 种菜的价格,方案数为不吃第 i 种菜的方案数 + 吃第 i 种菜的方案数

即:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - a[i]];

(3)若拥有的钱M小于第 i 种菜的价格,方案数就为用M吃前 i - 1 种菜的方案数

即:dp[i][j] = dp[i - 1][j];


根据以上思路完成状态转移方程的构建后,直接输出dp[N][M]即为答案。参考代码如下:

#include <iostream>
using namespace std;
int a[101], f[101][10001] = {0};

int dp(int n, int m) {
    //i表示有i种菜,j表示有j元;f[i][j]表示用j元吃前i种菜
    //a[i]表示第i种菜的价格
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            if (j == a[i]) {
            //若拥有的钱与第i种菜价格相同,方案数为吃前i-1种菜方案数+只吃第i种菜
                f[i][j] = f[i - 1][j] + 1;
            } else if (j > a[i]) {
            //若拥有的钱充足,方案数为不吃第i种菜+吃第i种菜
                f[i][j] = f[i - 1][j] + f[i - 1][j - a[i]];
            } else if (j < a[i]) {
            //若拥有的钱不够吃第i种菜,那么方案数就为吃前i-1种菜的方案数
                f[i][j] = f[i - 1][j];
            }
        }
    }
    return f[n][m];
}

int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }

    int res = dp(n, m);
    cout << res;
    return 0;
}

待续

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YUKIPEDIA~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值