題目鏈接:1258:【例9.2】數字金字塔
1258:【例9.2】數字金字塔
時間限制: 1000 ms 內存限制: 65536 KB
提交數: 9635 通過數: 5467
【題目描述】
觀察下面的數字金字塔。寫一個程序查找從最高點到底部任意處結束的路徑,使路徑經過數字的和最大。每一步可以從當前點走到左下方的點也可以到達右下方的點。
在上面的樣例中,從13到8到26到15到24的路徑產生了最大的和86。
【輸入】
第一個行包含R(1≤ R≤1000),表示行的數目。
后面每行為這個數字金字塔特定行包含的整數。
所有的被供應的整數是非負的且不大於100。
【輸出】
單獨的一行,包含那個可能得到的最大的和。
【輸入樣例】
5 13 11 8 12 7 26 6 14 15 8 12 7 13 24 11
【輸出樣例】
86
【動態規划基礎】數字金字塔
經典的動態規划水題
方法一:貪心
題目里說了,從上往下走,要求出路徑的最大值,那么我最先想到的方法就是——
你每次都走最大的一邊就可以了啊。
就像這樣:
但是,如果你是一個善於造數據來驗證你的算法的人,你很快就會發現自己算法的問題,就像這樣:
1 2 1 1 2 9 1 2 8 9 3 3 2 9 9
在這一組數據中,如果使用貪心算法,就會一開始就左邊,從而錯失下面全是9的右邊。
也就是說,貪心算法看得太近了,沒有顧全大局。
方法二:搜索 & 記憶化搜索
像我這樣的暴力型選手,碰見什么題都要用搜索寫一遍。可以對拍不說,寫不出來的時候,還可以騙分。
#include <bits/stdc++.h> using namespace std; int jzt[1005][1005], maxn, summ, n; void dfs(int i, int j){ if(i==n){ maxn=maxn>summ?maxn:summ; } else{ summ+=jzt[i+1][j]; dfs(i+1, j); summ-=jzt[i+1][j]; summ+=jzt[i+1][j+1]; dfs(i+1, j+1); summ-=jzt[i+1][j+1]; } } int main(){ scanf("%d", &n); for(int i=0; i<n; i++){ for(int j=0; j<=i; j++){ scanf("%d", &jzt[i][j]); } } dfs(0, 0); printf("%d", maxn+jzt[0][0]); return 0; }
T了。。
但是沒關系,我們還有搜索優化利器:記憶化。
通過觀察與推理,我們會發現,中間的這些點到底端的最優解會被重復計算,所以我們要把這些狀態記憶下來,四舍五入約等於全部記下來(其實是我懶得做特判)。。
#include <bits/stdc++.h> using namespace std; int n, bz[1005][1005], jzt[1005][1005]; int dfs(int x, int y){ if(x==n-1) return jzt[x][y]; else{ if(bz[x][y]) return jzt[x][y]; else{ bz[x][y]=1; jzt[x][y]+=max(dfs(x+1, y), dfs(x+1, y+1)); return jzt[x][y]; } } } int main(){ scanf("%d", &n); for(int i=0; i<n; i++){ for(int j=0; j<=i; j++){ scanf("%d", &jzt[i][j]); } } printf("%d", dfs(0, 0)); return 0; }
這次,就成功地AC了,不得不感嘆記憶化的強大。其實記憶化搜索的用時就已經相當於動態規划了(這也是為什么我把搜索學好后才學動態規划)。。
方法三:動態規划(遞推)
現在,想象一下,通過某些神奇的算法,你已經成功走到了倒數第二行的某一個位置,那么下一步應該怎么走呢?
很明顯,只要走最大的那個就可以了。因為這里只有最后一步了,局部最優解就等於全局最優解。
但事實是,你並不知道這個神奇的算法。。。所以你還是不知道你會走到倒數第二行的哪個位置。
但是,無論如何,你最終都會走到倒數第二行的n-1個點之一。那么我們就把到任意一個點的全局最優解求出來吧。
那么知道了倒數第二行怎么走,那么我們就再進一步吧。
通過某些神奇的算法,你已經成功走到了 n-2 行(倒數第三行)的某一個位置,那么下一步應該怎么走呢?
這時,我們就不能通過單純的比大小來決定了。
但是,我們通過上一步的計算,已經知道了走到 n-1 行的最優解,而且,我們也可以算出來走到 n-1 行某一個位置之后走到的點的和(這里指之前算出的最優解)。
那么問題就很簡單了,只需要走這個值最大的一邊就可以了。
重復這個過程。。。
最后:
通過某些神奇的算法,你已經成功走到了第 1 行的某一個位置,那么下一步應該怎么走呢?
顯然,我們已經知道了下一步該怎么走,,而且這時,我們就可以考慮這個“神奇的算法”了。
由於第一行只有一個數,所以我們可以肯定的是,我們走到的一定是這個位置。
最后,再算出第一步該怎么走時,你就算出了這個最優路徑。
代碼:
#include <bits/stdc++.h> using namespace std; int n, a[1005][1005]; int main(){ cin >> n; for(int i=0; i<n; i++){ for(int j=0; j<=i; j++){ cin >> a[i][j]; } } for(int i=n-2; i>=0; i--){ for(int j=0; j<=i; j++){ a[i][j] += max(a[i+1][j], a[i+1][j+1]); } } cout << a[0][0]; return 0; }