洛谷動態規劃習題整理(一)棋盤及揹包問題實例

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章