動態規劃進階篇

動態規劃進階篇

如果你已經熟悉了動態規劃的基礎知識,那麼接下來我們來討論二維的動態規劃吧!

問題:
給定一個N×M 的表格,每個格子上放着一個蘋果。如果你從最左上角的格子上開始走,每次只能向下或者向右走,每到一個格子你就把蘋果收集起來。這樣下去,你最多能夠收集多殺個蘋果??

解決這個問題與其它的DP問題並沒有其它的區別。
首先,我們要找到問題的“狀態”。我們要注意的是我們最多有兩種狀態去收集蘋果:從左邊(除了第一列)和從右邊(除了第一行)。因此爲了求出到達當前格子後最多能收集到多少個蘋果, 我們就要先去考察那些能到達當前這個格子的格子,到達它們最多能收集到多少個蘋果。 (是不是有點繞,但這句話的本質其實是DP的關鍵:欲求問題的解,先要去求子問題的解)

經過上面的分析,很容易得出問題的狀態S[i][j]表示到達格子(i, j)最多能收集到蘋果的個數。那麼狀態轉換方程爲:
S[i][j]=A[i][j]+max(S[i1][j],if i>0;S[i][j1],if j>0)
其中 i 代表行,j 代表列。A[i][j] 代表格子(i,j) 處蘋果的數量。
S[i][j] 的計算:1.對於每一行,從左向右計算,然後從上到下逐行處理;2. 對於每一列,從上到下計算,然後從左向右逐列處理。

僞代碼:

For i = 0 to N - 1
   For j = 0 to M - 1
   S[i][j] = A[i][j] +
      max(S[i][j-1], if j>0 ; S[i-1][j], if i>0 ; 0)

Output S[n-1][m-1]

LintCode 數字三角形

首先來一道簡單的題,練練手。

題目描述

給定一個數字三角形,找到從頂部到底部的最小路徑和。每一步可以移動到下面一行的相鄰數字上。
樣例
比如,給出下列數字三角形:

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]
從頂到底部的最小路徑和爲11 ( 2 + 3 + 5 + 1 = 11)。

對於動態規劃問題,首先我們要找到問題的“狀態”和“狀態轉換方程”。
對於該問題我們假設dp[i][j]爲從第1行元素到第i行第j個元素的最小路徑和。比如說dp[3][2]=5+min(2+3,2+4)=5+min(dp[2][1],dp[2][2]) , res[i]爲到第i行的最小路徑和。則有res[i]=min(dp[i][j]forj=0:dp[i].size()) ;
因爲到達元素(i, j)只能從上面相鄰的元素到達,因此有:
dp[i][j]=A[i][j]+min(dp[i1][j1] if i>0 and j>0,dp[i1][j] if i>0)
注意:下標從0開始

Talk is cheap, show me thecode!

int minimumTotal(vector<vector<int> > &triangle){
    int n = triangle.size();    //行數
    vector<vector<int>> dp(n); // 創建一個N行的二維數組
    dp[0].push_back(triangle[0][0]);
    // 從第二行元素開始,因爲第一行只有一個元素
    for (int i = 1; i < n; ++i) {
        int col = triangle[i].size(); //第i行的元素個數。。。其實 col = i+1;
        vector<int> tmp(col, 0);
        for (int j = 0; j < col; ++j) {
            // 注意邊界條件
            if (j == 0)
                tmp[j] = triangle[i][j] + dp[i-1][j];
            else if (j == col -1 )
                tmp[j] = triangle[i][j] + dp[i-1][j-1];
            else
                tmp[j] = triangle[i][j] + min(dp[i-1][j], dp[i-1][j-1]);
        }
        dp[i] = tmp;
    }

    int res = dp[n-1][0];
    for (int i = 1; i < dp[n-1].size(); ++i)
    {
        res = min(res,dp[n-1][i]);
    }
    return res;
}

TopCoder AvoidRoads

Problem Statement

In the city, roads are arranged in a grid pattern. Each point on the grid represents a corner where two blocks meet. The points are connected by line segments which represent the various street blocks. Using the cartesian coordinate system, we can assign a pair of integers to each corner as shown below.

這裏寫圖片描述

You are standing at the corner with coordinates 0,0. Your destination is at corner width,height. You will return the number of distinct paths that lead to your destination. Each path must use exactly width+height blocks. In addition, the city has declared certain street blocks untraversable. These blocks may not be a part of any path. You will be given a String[] bad describing which blocks are bad. If (quotes for clarity) “a b c d” is an element of bad, it means the block from corner a,b to corner c,d is untraversable. For example, let’s say
width = 6
length = 6
bad = {“0 0 0 1”,”6 6 5 6”}
The picture below shows the grid, with untraversable blocks darkened in black. A sample path has been highlighted in red.

這裏寫圖片描述
Constraints
- width will be between 1 and 100 inclusive.
- height will be between 1 and 100 inclusive.
- bad will contain between 0 and 50 elements inclusive.
- Each element of bad will contain between 7 and 14 characters inclusive.
- Each element of the bad will be in the format “a b c d” where,
a,b,c,d are integers with no extra leading zeros,
a and c are between 0 and width inclusive,
b and d are between 0 and height inclusive,
and a,b is one block away from c,d.
- The return value will be between 0 and 2^63-1 inclusive.

Examples
0)
6
6
{“0 0 0 1”,”6 6 5 6”}
Returns: 252
Example from above.

1)
1
1
{}
Returns: 2
Four blocks aranged in a square. Only 2 paths allowed.

2)
35
31
{}
Returns: 6406484391866534976
Big number.

3)
2
2
{“0 0 1 0”, “1 2 2 2”, “1 1 2 1”}
Returns: 0

先來看一下題目說了啥吧。給定一個width×height 的網格,從座標(0, 0)沿着網格線走到座標(width, height)一共有多少種走法。其中一些網格線是走不通的,走不通的網格線以字符串座標的形式給出。
注意:“Each path must use exactly width+height blocks”, 因此給定一個座標到下一個座標只能往右或者往上走。

動態規劃問題的解法只有兩個步奏:找到問題的“狀態”和“狀態轉換方程”。
對於這個問題如果說所有的網格線都是可以走的通的話,那麼個問題與上面收集蘋果的問題,有木有感覺解法是一樣的!!!!
首先,我們要知道問題的狀態:假設從座標(0,0)走到左邊(i, j)一共有dp(i,j) 中走法。
因爲走到座標(i,j) 處,只有兩種方式從左邊(i1,j) 或者從下面(i,j1) ,如果線路是通的話。因此,狀態轉換方程爲

dp[i][j]+={dp[i1[j],dp[i][j1],if i>0 and from (i-1, j) to (i, j) not badif j>0 and from (i, j-1) to (i, j) not bad
因此,現在關鍵的問題變成判斷(i1,j)(i,j1)(i,j) 是否是通的。現在提供一種比較簡答的方法。將座標(i1,j),(i,j) 轉換成字符串,然後在判斷該字符串是否在給定的bad string裏。
Talk is cheap, show me the code!

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>

using namespace std;
//將座標解析成字符串
string parse(int x, int y, int i, int j) {
    string str;
    str = string(1,x+'0') + " " + string(1, y+'0') + " ";
    str += string(1,i+'0') + " " + string(1, j+'0');
    return str;
}
/* brief: 判斷從座標的左邊(x-1, y)或者下面(x, y-1)是否可以到達座標(x, y)
*param x, y :座標(x, y)
*param bad: 不通的路徑
*param str: 取"Left"表示判斷從(x-1, y)到(x,y)是否爲通。"Down"表示從下面
**/
bool canReach(int x, int y, vector<string> &bad, string str)
{
    int i = x, j = y;
    if (str == "Left") i = x-1;
    if (str == "Down") j = y - 1;
    auto iter = find(bad.begin(), bad.end(), parse(i, j, x, y));
    if (iter != bad.end()) {    //說明該路徑不可達
        // 該路徑我們已經使用過了,則我們可以刪除提高查找效率
        bad.erase(iter);  
        return false;
    }
    //注意題目中的陷阱
    else if ((iter = find(bad.begin(), bad.end(),
                          parse(x, y,i,j))) != bad.end()) {
        bad.erase(iter);
        return false;
    }
    return true;
}

/** brief: 判斷從座標(0,0)到座標(width, height)有多少種路徑
*param bad: 不同的路徑座標
*/
long long numWays(int width, int height, vector<string> bad)
{
    // 創建一個二維數組並且初始化爲0
    vector<vector<long long>> dp(width+1, vector<long long>(height+1, 0));
    dp[0][0] = 1; //邊界條件
    for (int i = 0; i <= width; ++i) {
        for (int j = 0; j <= height; ++j) {
            if (i > 0 && canReach(i, j, bad, "Left"))
                dp[i][j] += dp[i-1][j];
            if (j > 0 && canReach(i, j, bad, "Down"))
                dp[i][j] += dp[i][j-1];
        }
    }
    return dp[width][height];
}

int main()
{

    vector<string> bad {"0 0 0 1","6 6 5 6"};
    cout << numWays(6, 6, bad) << endl;

    return 0;
}

Reference:
[1] 動態規劃:從新手到專家
[2] Dynamic Programming – From Novice to Advanced

發佈了49 篇原創文章 · 獲贊 22 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章