动态规划入门

例题(题目来源:洛谷)
1.P1002 过河卒
2.P1359 租用游艇

1
题目:
棋盘上 A 点有一个过河卒,需要走到目标 B 点。卒行走的规则:可以向下、或者向右。同时在棋盘上 C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。 棋盘用坐标表示,A 点 (0,0)、B 点 (n,m),同样马的位置坐标是需要给出的。 现在要求你计算出卒从 A 点能够到达 B 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。 现在要求你计算出卒从 A 点能够到达 B 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
图片来自洛谷
思路:
第一眼看到这个题感觉暴搜应该也能过,记忆化搜索也行,dp也可以,但博主正在练习入门dp,所以就用了dp。
由原点向下推,比如到达第二个点有2条到达第3个点有1条第四个点又只能从第二个和第三个到达,那么到达第四个点有几条路线呢?显然就是2+1条。那么递推下去求到最后一个点,就可以得到所有的路线。
我们可以知道卒的行动路线,即向右或向下,因此我们能够很直观的看出来每个点可以由前面哪些到达。举个例子,坐标为(2,2)的点由(2,1)和(1,2)到达,因此可以写出动态方程dp[i][j] = max(dp[i-1][j] + dp[i][j-1], dp[i][j]);
这个题有个边界限制,不可以超出棋盘,我们可以这样做:在读如的时候拓宽棋盘边界,即多出来一圈,但多出来的也会被加上,但是如果是+1的话那么就不影响了。
下面是代码:

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 500;
long long dp[maxn][maxn]; //dp[i][j]表示到达(i,j)点的路径 要开long long不然可能WA
bool cannot[maxn][maxn];//马可能到的点
int  n, m, horse_x, horse_y;
int dx[] = {0, -1, 1, -1, 1, -2, 2, -2, 2};
int dy[] = {0, -2, -2, 2, 2, -1, -1, 1, 1};
int main() {
    cin >> n >> m >> horse_x >> horse_y;
    n += 1, m += 1, horse_x += 1, horse_y += 1; //+1防止越界
    for (int i = 0; i < 9; i++) {
        int xx = horse_x + dx[i], yy = horse_y + dy[i];
        if ((xx >= 0 && xx <= n) && (yy >= 0 && yy <= m)) {
            cannot[xx][yy] = 1;
        }
    }
    dp[1][1] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (cannot[i][j]) {
                continue;
            } else {
                dp[i][j] = max(dp[i-1][j] + dp[i][j-1], dp[i][j]);
            }
        }
    }
    cout << dp[n][m] << endl;
    return 0;
}

一个类似的练习 P1130 红牌

2
题目:
长江游艇俱乐部在长江上设置了n 个游艇出租站1,2,…,n。游客可在这些游艇出租站租用游艇,并在下游的任何一个游艇出租站归还游艇。游艇出租站i 到游艇出租站j 之间的租金为r(i,j),1<=i<=j<=n。试设计一个算法,计算出从游艇出租站1 到游艇出租站n 所需的最少租金。 对于给定的游艇出租站i 到游艇出租站j 之间的租金为r(i,j),1<=i<j<=n,编程计算从游艇出租站1 到游艇出租站n所需的最少租金。 保证计算过程中任何时刻数值都不超过10^6
思路:
先读入矩阵
接下来是动态方程,dp[i]表示从1到i所需要的最小值
问题就在于我们如何判断最小值,我们应该用什么判断呢?
因为从1出发,所以dp[1]=0,不做讨论,从前往后推,即从第二个开始
每个的最小值等于多少呢?显然一个循环是不足以让我们求出最小值,
我们需要另一个循环,即从1开始遍历到i-1。
以4为例
第一次:dp[4] = min(dp[4], dp[1]+a[1][4]); dp[4] 更新一次
第二次:dp[4] = min(dp[4], dp[2]+a[2][4]); 第一次是从这三个中选一个更小的。

第i-1次:dp[i] = min(dp[i], dp[i-1]+a[i-1][i]); 就是之前所有情况都已经考虑了一遍,筛选出了最小值。
下面是代码:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1e3;
int n;
int a[maxn][maxn], dp[maxn];
int main() {
    cin >> n;
    for (int i = 1; i < n; i++) {
        for (int j = i+1; j <= n; j++) {
            cin >> a[i][j];
        }
    }
    for (int i = 2; i <= n; i++) {
        dp[i] = a[1][i];
        for (int j = 1; j < i; j++) {
            dp[i] = min(dp[i], dp[j]+a[j][i]);
        }
    }
    cout << dp[n];
    return 0;
}

欢迎各位看的大佬指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

max_tanAlpha

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

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

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

打赏作者

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

抵扣说明:

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

余额充值