洛谷动态规划习题整理(一)棋盘及揹包问题实例

P1002 过河卒

题目描述

  棋盘上A点有一个过河卒,需要走到目标B点。卒行走的规则:可以向下、或者向右。同时在棋盘上C点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
  棋盘用座标表示,A点(0,0)、B点(n,m),同样马的位置座标是需要给出的。
  现在要求你计算出卒从A点能够到达B点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。

  • 输入:一行四个正整数,分别表示B点座标和马的座标。
  • 输出:一个整数,表示所有的路径条数。

解题步骤

  • 初始化棋盘边界为1
  • 状态方程:f[i][j] = f[i-1][j] + f[i][j-1]

注意事项

  • 初始化数组时应 +1
  • 状态方程的值会超过 int,可定义为 unsigned long long
  • 考虑“马”处于横向边界时,该位置的右侧均初始化为0;处于纵向边界时,该位置的下方均初始化为0。
  • 考虑“马”可一步跳到的位置处于横向纵向边界的情况,即“马”距离边界1或2的情况。
  • 更新状态方程时,考虑经过位置是“马”一步跳到的位置时,该位置上的值应为0(不更新)。

提交代码

#include<iostream>
#include<math.h>
using namespace std;

int main() {
    int bx, by, hx, hy; // b->B点,h->“horse”马的位置
    cin >> bx >> by >> hx >> hy;
    unsigned long long **map = new unsigned long long *[bx + 1];
    for (int i = 0; i <= bx; i++)  //动态二维数组
        map[i] = new unsigned long long[by + 1];
    for (int i = 0; i <= bx; i++)
            map[i][0] = 1; //x轴边界初始化
    for (int j = 0; j <= by; j++)
            map[0][j] = 1; //y轴边界初始化
    if (hx == 0) { //马在x边界
        for (int j = hy; j <= by; j++)
            map[0][j] = 0;
    }
    else if (hx == 1) { //马可跳到的位置在y边界(注:我没写错,就是y边界)
        if (hy-2 >= 0) {
            for (int j = hy-2; j <= by; j++)
                map[0][j] = 0;
        }
        else if (hy + 2 <= by) {
            for (int j = hy + 2; j <= by; j++)
                map[0][j] = 0;
        }
    }
    else if (hx == 2) { //马可跳到的位置在y边界
        if (hy - 1 >= 0) {
            for (int j = hy - 1; j <= by; j++)
                map[0][j] = 0;
        }
        else if (hy + 1 <= by) {
            for (int j = hy + 1; j <= by; j++)
                map[0][j] = 0;
        }
    }
    if (hy == 0) { //马在y边界
        for (int i = hx; i <= bx; i++)
            map[i][0] = 0
    }
    else if (hy == 1) { //马可跳到的位置在x边界
        if (hx - 2 >= 0) {
            for (int i = hx - 2; i <= bx; i++)
                map[i][0] = 0;
        }
        else if (hx + 2 <= bx) {
            for (int i = hx + 2; i <= bx; i++)
                map[i][0] = 0;
        }
    }
    else if (hy == 2) { //马可跳到的位置在x边界
        if (hx - 1 >= 0) {
            for (int i = hx - 1; i <= bx; i++)
                map[i][0] = 0;
        }
        else if (hx + 1 <= bx) {
            for (int i = hx + 1; i <= bx; i++)
                map[i][0] = 0;
        }
    }
    for (int i = 1; i <= bx; i++) {
        for (int j = 1; j <= by; j++) {
            if ((abs(i - hx) == 1 && abs(j - hy) == 2) || (abs(i - hx) == 2 && abs(j - hy) == 1) || (i == hx && j == hy))
                map[i][j] = 0;
            else
                map[i][j] = map[i - 1][j] + map[i][j - 1];
        }
    }
    cout << map[bx][by];
    return 0;
}

P1044 栈

题目描述

  宁宁考虑的是这样一个问题:一个操作数序列“1,2,…,n”,栈A的深度大于n。现在可以进行两种操作:
1. 将一个数,从操作数序列的头端移到栈的头端(对应数据结构栈的push操作)
2. 将一个数,从栈的头端移到输出序列的尾端(对应数据结构栈的pop操作)
  使用这两种操作,由一个操作数序列就可以得到一系列的输出序列,你的程序将对给定的n,计算并输出由操作数序列1,2,…,n经过操作可能得到的输出序列的总数。

  • 输入:一个整数n(1≤n≤18)
  • 输出:可能输出序列的总数目

解题步骤

在这里插入图片描述

  • 如图所示,规定从A到B只能够向右或向上移动,右移为入栈操作,上移为出栈操作,所求A点到B点的路径数目就是N个节点出栈序列的数目,并且从A点到B点的每一条路都代表一种出栈序列。
  • 入栈次数>=出栈次数,向右次数>=向上次数。故只需右下部分矩阵(含对角线上的点)。
  • 转化为棋盘的动态规划问题,模仿“P1002过河卒”解决,注意该棋盘只有右下部分。

提交代码

#include<iostream>  
using namespace std;  

int main() {  
    int n;  
    cin >> n;  
    unsigned long long **map = new unsigned long long *[n + 1];  
    for (int i = 0; i <= n; i++)  
        map[i] = new unsigned long long[n + 1];  
    for (int j = 0; j <= n; j++) //初始化下边界  
            map[0][j] = 1;  
    for (int i = 1; i <= n; i++) {   
        for (int j = 1; j <= n; j++) {  
            if (j > i)  
                map[i][j] = map[i - 1][j] + map[i][j - 1];  
            else if (j == i)  
                map[i][j] = map[i - 1][j];  
            else  
                continue;  
        }  
    }  
    cout << map[n][n];  
    return 0;  
}  

参考博客


P1048 采药

题目描述

  辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

  • 输入:第一行有2个整数T(1≤T≤1000)和M(1≤M≤100),用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。接下来的M行每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
  • 输出:1个整数,表示在规定的时间内可以采到的草药的最大总价值。

解题步骤

  • 典型的0/1揹包问题
  • 遍历考虑前i个物品的情况下的最优解
  • 如果装不下当前物品,那么前i个物品的最佳组合和前i-1个物品的最佳组合是一样的。
  • 如果装得下当前物品,有以下两种假设,选取其中较大价值的一个,为当前最佳组合的价值。
    • 假设1:装当前物品,在给当前物品预留了相应空间的情况下,前i-1个物品的最佳组合加上当前物品的价值就是总价值。
    • 假设2:不装当前物品,那么前i个物品的最佳组合和前i-1个物品的最佳组合是一样的。
  • 注释部分包含了回溯输出索取物品是哪些
    • 从后向前遍历n个物品
    • 剩余空间相同的情况下,若前i个物品的组合与前i-1个物品的组合相同,则说明未装当前物品。
    • 若当前组合符合上述的假设1,则说明装了当前物品,输出即可。
    • 注意,找出一个物品后,应将剩余空间减少当前物品的重量。

提交代码

#include<iostream>
using namespace std;

int main() {
    int t, m;
    cin >> t >> m;
    int * cost = new int[m + 1];
    int * value = new int[m + 1];
    for (int i = 1; i <= m; i++)
        cin >> cost[i] >> value[i];
    cost[0] = 0;
    value[0] = 0;
    int ** dp = new int*[m + 1];
    for (int i = 0; i <= m; i++) {
        dp[i] = new int[t + 1];
        for (int j = 0; j <= t; j++)
            dp[i][j] = 0;
    }
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= t; j++) {
            if (j >= cost[i])
                dp[i][j] = dp[i - 1][j] > dp[i - 1][j - cost[i]] + value[i] ? dp[i - 1][j] : dp[i - 1][j - cost[i]] + value[i];
            else
                dp[i][j] = dp[i - 1][j];
        }
    }
    cout << dp[m][t];
    // 回溯揹包中的物品是哪些
    /*
    cout << endl << "What are they?" << endl;
    int i = m, j = t;
    while (i > 0) {
        if (dp[i][j] == dp[i - 1][j]) {
            i--;
            continue;
        }
        else if (dp[i][j] == dp[i - 1][j - cost[i]] + value[i]) {
            cout << cost[i] << " " << value[i] << endl;
            j = j - cost[i];
        }
        i--;
    }
    */
    return 0;
}

参考博客


P1049 装箱问题

题目描述

  有一个箱子容量为V(正整数,0≤V≤20000),同时有n个物品(0<n≤30,每个物品有一个体积(正整数)。要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。

  • 输入:1个整数,表示箱子容量1个整数,表示有n个物品,接下来n行,分别表示这n个物品的各自体积。
  • 输出:1个整数,表示箱子剩余空间。

解题步骤

  • 典型0/1揹包问题
  • 将物品体积同时看作空间消耗和价值即可
  • 代码同上题,仅需修改读入参数即可,不再赘述。

P1060 开心的金明

题目描述

  金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的N元。于是,他把每件物品规定了一个重要度,分为5等:用整数1−5表示,第5等最重要。他还从因特网上查到了每件物品的价格(都是整数元)。他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。

  • 输入:第一行,为2个正整数,用一个空格隔开:nm(其中N(<30000)表示总钱数,m(<25)为希望购买物品的个数。)从第2行到第m+1行,第j行给出了编号为j−1的物品的基本数据,每行有2个非负整数vp(其中v表示该物品的价格(v≤10000),p表示该物品的重要度(1−5)
  • 输出:1个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<100000000)。

解题步骤

  • 典型0/1揹包问题
  • 代码仅需修改 dp[i][j] = dp[i - 1][j] > dp[i - 1][j - v[i]] + v[i] * w[i] ? dp[i][j] = dp[i - 1][j] : dp[i - 1][j - v[i]] + v[i] * w[i]; 即可,不再赘述。
发布了5 篇原创文章 · 获赞 2 · 访问量 1835
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章