一句話題目:給出一個n層的三角形,每個位置有一個數字,到達后可獲得,求到達最低層能達到的最大數字和。
題目分析:
首先我們考慮能不能用搜索做,因為對於一個坐標,我們只有向下的左邊或者右邊。對於一個三角形我們進行特殊的處理,比如下面的三角形我們可以處理成
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11
如果我們到達了一個位置(i,j)我們繼續向下可以到達(i+1,j+1)和(i+1,j);
那么搜索的代碼:
#include <cstdio> const int maxn = 1010; int n, ans, cnt; int val[maxn][maxn]; void dfs(int i,int j){ if (i == n+1){ if (cnt > ans) ans = cnt; return ; } cnt += val[i][j]; dfs(i+1, j+1);dfs(i+1, j); cnt -= val[i][j]; } int main(){ scanf("%d", &n); for (int i = 1;i <= n;i ++) for (int j = 1;j <= i;j ++) scanf("%d", &val[i][j]); dfs(1, 1); printf("%d", ans); }
復雜度是2^n很明顯超時(題目中n為1000);
我們看一個簡單的情況比如我們在(i,j)這個位置,這個位置是由(i-1,j)轉移過來,我們已經求出了所有的(i,j)以下的點我們向上返回到(i-1,j)這個位置,我們就向下達到(i,j+1)我們發現(i,j)可以到達(i+1,j+1)並且(i,j+1)也可以到達(i+1,j+1);那么這個點我們就計算了兩次,導致效率低下,如果我們用一個數組表示到達(i,j)能得到的最大數值,就可以不用重復計算,這樣就相當於把每個點都遍歷一遍,復雜度就是((1+n)*n/2) 就不會超時了,我們把這個方法,叫做記憶化,而整個方法叫做記憶化搜索。
下面看代碼:
#include <cstdio> #include <cstring> #include <iostream> const int maxn = 1010; using namespace std; int n; int val[maxn][maxn], f[maxn][maxn]; int dfs(int i,int j){ if (f[i][j] != -1) return f[i][j]; if (i == n+1) return f[i][j] = val[i][j]; return f[i][j] = max(dfs(i+1, j+1), dfs(i+1, j)) + val[i][j]; } int main(){ memset(f, -1, sizeof(f)); scanf("%d", &n); for(int i = 1;i <= n;i ++) for (int j = 1;j <= i;j ++) scanf("%d", &val[i][j]); printf("%d", dfs(1,1)); }
但是因為遞歸比遞推本身慢所以我們平時都不寫遞歸,可以寫成遞推。我們前面講到的是f[i][j]表示從(i,j)出發能達到的最大值,現在我們換種方式,用f[i][j]表示從(1,1)出發到達(i,j)的最大值,因為我們發現(i,j)可以從(i-1,j)和(i-1,j-1)轉移過來所以我們按照f[i][j] = max(f[i-1][j-1], f[i-1][j]) + val[i][j];可以得到當前點的最大值,那么因為我們在計算f[i][j]的時候一定要保證f[i-1][j-1]和f[i-1][j]已經計算過。所以我們可以采用順推的方法就是從上向下推。下面看代碼:
#include <cstdio> #include <cstring> #include <iostream> const int maxn = 1010; using namespace std; int n, ans; int val[maxn][maxn], f[maxn][maxn]; int main(){ scanf("%d", &n); for (int i = 1;i <= n;i ++) for (int j = 1;j <= i;j ++) scanf("%d", &val[i][j]); for (int i = 1;i <= n;i ++) for (int j = 1;j <= i;j ++){ if (j == 1) f[i][j] = val[i][j] + f[i-1][j]; else if (j == n) f[i][j] = val[i][j] + f[i-1][j-1]; else f[i][j] = max(f[i-1][j], f[i-1][j-1]) + val[i][j]; } for (int i = 1;i <= n;i ++) ans = max(ans, f[n][i]); printf("%d", ans); }
考慮一個新的思想,從上到下求最大值,可以轉換為從最后一排出發到達第一層的最大值,那么對於(i,j)可以從(i+1, j+1)和(i+1, j)過來,那么可以將整個遞推倒過來寫,就可以 不用最后一個for循環了。
代碼:
#include <cstdio> #include <iostream> const int maxn = 1010; using namespace std; int n; int f[maxn][maxn], val[maxn][maxn]; int main(){ scanf("%d", &n); for (int i = 1;i <= n;i ++) for (int j = 1;j <= i;j ++) scanf("%d", &val[i][j]); for (int i = n;i >= 1;i --) for (int j = 1;j <= i;j ++) f[i][j] = max(f[i+1][j], f[i+1][j+1]) + val[i][j]; printf("%d", f[1][1]); return 0; }
下面對於動態規划進行總結:
1.首先定義狀態,考慮有關的變量,然后進行定義
2.找出轉移方程
3.進行優化,一般就是狀態優化和轉移優化。