動態規劃演算法理解——從例題中學習

是桑大神呀發表於2020-11-13

這些題目的難度是遞增順序來的,後續慢慢再補

第一題(牛妹的蛋糕)

題目描述

眾所周知,牛妹非常喜歡吃蛋糕。
第一天牛妹吃掉蛋糕總數三分之一(向下取整)多一個,第二天又將剩下的蛋糕吃掉三分之一(向下取整)多一個,以後每天吃掉前一天剩下的三分之一(向下取整)多一個,到第n天準備吃的時候只剩下一個蛋糕。
牛妹想知道第一天開始吃的時候蛋糕一共有多少呢?

示例1
輸入
複製
2
返回值
複製
3

思路:

1.(數學分析)首先假設第一天準備開始吃的時候有x份蛋糕,那麼第二天準備開始吃的時候的蛋糕 x-(x*1/3 + 1) = x * 2/3 - 1 份蛋糕。
2.(找狀態方程)設dp[i]為第i天準備開始吃的時候有dp[i]份蛋糕,那麼第二天準備開始吃的時候的蛋糕就為dp[i+1];根據1的分析可得 dp[i+1] = dp[i] * 2/3 - 1;
3.因為題目給定的n代表的是,最後一天她正準備吃的時候剩下的數量,我們要根據這個數量往前推出她第一天的蛋糕數。所以可得方程式dp[i] = 3 * (dp[i+1]+1) / 2; (就是2中的方程變形)。

完事寫程式碼

#include<iostream>
using namespace std;

 int cakeNumber(int n) {
    int dp = 1;
    for(int i = n - 1; i >= 1; --i)
    {
        dp = 3 * (dp + 1) / 2;
    }
	return dp;
}
int main()
{
	int n;
	cin >> n;
	cout << cakeNumber(n);
	return 0;	
} 

第二題(牛妹的禮物)

題目描述
眾所周知,牛妹有很多很多粉絲,粉絲送了很多很多禮物給牛妹,牛妹的禮物擺滿了地板。
地板是N\times MN×M的格子,每個格子有且只有一個禮物,牛妹已知每個禮物的體積。
地板的座標是左上角(1,1) 右下角(N, M)。
牛妹只想要從屋子左上角走到右下角,每次走一步,每步只能向下走一步或者向右走一步或者向右下走一步
每次走過一個格子,拿起(並且必須拿上)這個格子上的禮物。
牛妹想知道,她能走到最後拿起的所有禮物體積最小和是多少?

思路

首先把題目給定的禮物體積存入一個二維陣列A[MAX][MAX]中,然後準備開始動態規劃了。設一個二維陣列dp[MAX][MAX],先初始化計算它的第一行第一列,因為第一行第一列的所有資料只能是通過它的前一個資料得來,初始完之後,計算其他的。
狀態方程 :dp[i][j] = min(dp[i-1][j-1],min(dp[i][j-1],dp[i-1][j])) + A[i][j];
就是判斷當前點的向上一個資料,向左的一個資料,左上的一個資料,這三個資料的最小值再加上當前的體積值就是該點的值。

完事寫程式碼

#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
#define MAX 1100
int dp[MAX][MAX],A[MAX][MAX];

int main()
{
	int m,n;
	cin >> n >> m;
	for(int i = 1; i <= n; ++i){
		for(int j = 1; j <= m; ++j){
			cin >> 	A[i][j];	
			dp[i][j] = 0; 
		}		
	}	
	dp[1][1] = A[1][1];
	for(int i = 2; i <= n; ++i){
		dp[i][1] = dp[i-1][1] + A[i][1];
	}
	for(int i = 2; i <= m; ++i){
		dp[1][i] = dp[1][i-1] + A[1][i];
	}
	for(int i = 2; i <= n; ++i){
		for(int j = 2; j <= m; ++j){
			dp[i][j] = min(dp[i-1][j-1],min(dp[i-1][j],dp[i][j-1])) + A[i][j];
		}
	}
	cout << dp[n][m];
	return 0;
}

第三題(變相)

題目描述
牛牛準備在一個3行n列的跑道上跑步。一開始牛牛可以自己選擇位於(1,1)還是(2,1)還是(3,1)。

跑道的每一格都有一些金幣,當牛牛跑到一個格子,他會獲得這個格子的所有金幣。

當牛牛位於第i行第j列時,他可以的下一步最多可能有三種選擇:

  1. 不花費金幣跑到第i行第j+1列
  2. 花費m_jm
    j

    的金幣跑到第i-1行第j+1列(如果i=1則不可以這麼跑)。
  3. 花費m_jm
    j

    的金幣跑到第i+1行第j+1列(如果i=3則不可以這麼跑)。
    (牛牛是一個富豪,本身帶了很多的金幣,所以你不用擔心他錢不夠用)
    現在告訴你所有格子的金幣數量和每一列的金幣花費,牛牛想知道他跑到第n列最多可以賺得多少金幣(賺得金幣=獲得金幣-消耗金幣)

思路

首先用一個二維陣列A[3][MAX]把每個格子的金幣數儲存起來,然後就是初始化dp[][]陣列了。 這個題目的初始化只能初始化dp的第一列,和上一題不同,這個題後面的數都是可以變化的。這題的遍歷只能一列一列的遍歷,不能一行行的來(畫個圖,拿資料推一遍你就知道了。)
狀態方程:
i == 0:(就是比較一下該點左邊和它左下減去跳行的金幣後的大小)
dp[i][j] = max(dp[i][j-1],dp[i+1][j-1] - m[j-1]) + A[i][j];
i == 1:(比較左邊,左上左下減去跳行的金幣後的大小)
dp[i][j] = max(dp[i][j-1],max(dp[i-1][j-1],dp[i+1][j-1])-m[j-1]) + A[i][j];
i == 2:(比較左邊,左上減去跳行的金幣後的大小)
dp[i][j] = max(dp[i][j-1],dp[i-1][j-1] - m[j-1]) + A[i][j];

測試資料
3
1 9 3
6 4 6
1 1 5
3 2 1 
#include<bits/stdc++.h>
using namespace std;
#define MAX 1100

int n;
int A[3][MAX],dp[3][MAX],m[MAX];

int main()
{
	cin >> n;
	for(int i = 0; i < 3; ++i){
		for(int j = 0; j < n; ++j){
			cin >> A[i][j];
		}
	}
	for(int i = 0; i < n; ++i){
		cin >> m[i];
	}
	dp[0][0] = A[0][0]; dp[1][0] = A[1][0]; dp[2][0] = A[2][0];
	for(int j = 1; j < n; ++j){
		for(int i = 0; i < 3; ++i){
			if(i == 1){
                dp[i][j] = max(dp[i][j-1],max(dp[i-1][j-1],dp[i+1][j-1])-m[j-1]) + A[i][j];   
	        }
	        else if(i == 0){
	            dp[i][j] = max(dp[i][j-1],dp[i+1][j-1] - m[j-1]) + A[i][j];
	        } 
	        else{
	            dp[i][j] = max(dp[i][j-1],dp[i-1][j-1] - m[j-1]) + A[i][j];
	        }
		}
	}
	int t = -100;
    for(int i = 0; i < 3; ++i){
        if(dp[i][n-1] > t) t = dp[i][n-1];
    }
    cout << t << endl;
	return 0;	
}

第四題(簡單變相)

題目描述
牛牛準備在一個3行n列的跑道上跑步。一開始牛牛位於(1,1)。

當牛牛位於第i行第j列時,他可以的下一步最多可能有三種選擇:

  1. 跑到第i行第j+1列
  2. 跑到第i-1行第j+1列(如果i=1則不可以這麼跑)。
  3. 跑到第i+1行第j+1列(如果i=3則不可以這麼跑)。

跑道上有一些格子設定了路障(一個格子可能有多個路障),牛牛不能跑到路障上。現在牛牛想知道,從(1,1)到(3,n)有多少條不同的路徑?

為了防止答案過大,答案對1e9+7取模。

思路

跟第三題差不多,dp[i][j] 代表的是該格子有幾條路可到達。(牛客上是提交核心程式碼)

const int mod = 1e9+7;
class Solution {
public:
    int solve(int n, int m, vector<int>& x, vector<int>& y) {
         long long dp[4][n];
         memset(dp,0,sizeof(dp));
         for(int i = 1; i <= m; ++i){
              dp[x[i - 1]][y[i - 1]] = -1;
         }
        dp[1][1] = 1; dp[2][1] = 0; dp[3][1] = 0;
      	for(int i = 2; i <= n; ++i){
      		dp[1][i] = dp[1][i] == -1 ? dp[1][i] = 0 : dp[1][i] = (dp[i][j-1]+dp[i+1][j-1]) % mod;
			dp[2][i] = dp[2][i] == -1 ? dp[2][i] = 0 : dp[1][i] = (dp[i][j-1]+dp[i-1][j-1]+dp[i+1][j-1]) % mod;
			dp[3][i] = dp[3][i] == -1 ? dp[3][i] = 0 : dp[1][i] = (dp[i][j-1]+dp[i-1][j-1]) % mod;	
		}
         return dp[3][n];
    }
};

相關文章