線性 DP - 數字三角形模型
原題 : 數字三角形
https://www.acwing.com/problem/content/900/
題意
給定一個如下圖所示的數字三角形,從頂部出發,在每一結點可以選擇移動至其左下方的結點或移動至其右下方的結點,一直走到底層,要求找出一條路徑,使路徑上的數字的和最大。
分析
對於每一個位置 (x, y), 只有兩種可能,向右下走或者向下走
狀態表示: f[x,y]
表示所有從 (x, y) 到 (n,n) 的路徑,指標是路徑數字和
狀態計算: f[x,y] = max(f[x+1, y], f[x+1, y+1]) + nums[x,y]
(x, y) 處狀態的計算依賴於 (x+1, y) 和 (x+1, y+1) 處的計算,由此按照子問題圖的拓撲序構建迴圈。
程式碼
#include <iostream> #include <algorithm> using namespace std; const int N = 510; int nums[N][N]; int f[N][N]; int main(){ int n; cin >> n; for(int i = 1; i <= n; i ++){ for(int j = 1; j <= i; j ++){ cin >> nums[i][j]; } } for(int x = n; x >= 1; x --) for(int y = 1; y <= x; y ++){ f[x][y] = max(f[x+1][y], f[x+1][y+1]) + nums[x][y]; } cout << f[1][1] << endl; return 0; }
衍生題1 : 摘花生
https://www.acwing.com/problem/content/1017/
題意
Hello Kitty想摘點花生送給她喜歡的米老鼠。
她來到一片有網格狀道路的矩形花生地(如下圖),從西北角進去,東南角出來。
地裡每個道路的交叉點上都有種著一株花生苗,上面有若干顆花生,經過一株花生苗就能摘走該它上面所有的花生。
Hello Kitty只能向東或向南走,不能向西或向北走。
問Hello Kitty最多能夠摘到多少顆花生。
分析
狀態表示,f[i,j]
表示 (1,1) 到 (i, j) 的所有路徑,指標是經過路徑上的花生數最大值。
狀態計算:只能從左邊或者上面走到當前位置f[i,j] = max(f[i-1, j], f[i, j-1]) + w[i,j]
程式碼
#include <iostream>
using namespace std;
const int N = 110;
int f[N][N], w[N][N];
int main(){
int r, c, t;
cin >> t;
while(t --){
cin >> r >> c;
for(int i = 1; i <= r; i ++){
for(int j = 1; j <= c; j ++){
cin >> w[i][j];
}
}
f[1][1] = w[1][1];
for(int i = 2; i <= r; i ++){
f[i][1] = f[i-1][1] + w[i][1];
}
for(int i = 2; i <= c; i ++){
f[1][i] = f[1][i-1] + w[1][i];
}
for(int i = 2; i <= r; i ++)
for(int j = 2; j <= c; j ++){
f[i][j] = max(f[i-1][j] , f[i][j-1]) + w[i][j];
}
cout << f[r][c] << endl;
}
return 0;
}
衍生題 2 : 最低通行費
https://www.acwing.com/problem/content/1020/
題意
一個商人穿過一個 N×N
的正方形的網格,去參加一個非常重要的商務活動。
他要從網格的左上角進,右下角出。
每穿越中間 1 個小方格,都要花費 1個單位時間。
商人必須在 (2N−1)個單位時間穿越出去。
而在經過中間的每個小方格時,都需要繳納一定的費用。
這個商人期望在規定時間內用最少費用穿越出去。
請問至少需要多少費用?
注意:不能對角穿越各個小方格(即,只能向上下左右四個方向移動且不能離開網格)。
分析
這個題乍一看跟數字三角形好像有點不太一樣,因為題目沒有限制只能向下向右,但是分析限制條件最多走 2 N - 1 步,如果只向下向右,最少也要走 2 N - 1 步,因此只能向下向右走。
狀態表示: f[i,j]
從 (1,1) 到 (i, j) 的所有路徑,指標是費用和的最小值
狀態計算:f[i,j] = min(f[i-1,j], f[i, j-1]) + w[i,j]
程式碼
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int f[N][N], w[N][N];
int main(){
int n;
cin >> n;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
cin >> w[i][j];
f[1][1] = w[1][1];
for(int i = 2; i <= n; i ++){
f[i][1] = f[i-1][1] + w[i][1];
f[1][i] = f[1][i-1] + w[1][i];
}
for(int i = 2; i <= n; i ++)
for(int j = 2; j <= n; j ++){
f[i][j] = min(f[i-1][j], f[i][j-1]) + w[i][j];
}
cout << f[n][n] << endl;
return 0;
}
衍生題3 : 方格取數
https://www.acwing.com/problem/content/description/1029/
題意
一個二維的矩陣,要尋找兩條從 (1, 1) 到 (n, n) 的路徑,使得兩條路徑數字和最大,一條路徑取過一個位置之後這個位置的數字就變成 0
分析
狀態表示f[k, i1, i2]
表示從 (1, 1) 到 (i1 ,k-i1) 和 (i2, k-i2) 的路徑,指標是路徑數字和最大值。
狀態計算,每個路徑上一步有兩種走法,組合起來有四種走法f[k,i1, i2] = max(f[k-1, i1-1, i2], f[k-1, i1, i2-1], f[k-1, i1-1, i2-1], f[k-1, i1, i2]) + t
t = w[i1][k-i1] + w[i2][k-i2] if i1 != i2, t = w[i1][k-i1] if i1 == i2
程式碼
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 22;
int f[N][N][N], w[N][N];
int main(){
int n;
cin >> n;
int a,b,c;
while(cin >> a >> b >> c, a || b || c)w[a][b] = c;
for(int k = 2; k <= n + n; k ++)
for(int i1 = 1; i1 < k; i1 ++)
for(int i2 = 1; i2 < k; i2++){
int j1 = k - i1, j2 = k - i2;
if(j1 <= n && j1 >=1 && j2 <= n && j2 >=1){
int t = w[i1][j1];
if(i1 != i2)t += w[i2][j2];
int& x = f[k][i1][i2];
x = max(x, f[k-1][i1-1][i2] + t);
x = max(x, f[k-1][i1-1][i2-1] + t);
x = max(x, f[k-1][i1][i2-1] + t);
x = max(x, f[k-1][i1][i2] + t);
}
}
cout << f[n + n][n][n] << endl;
return 0;
}
練習 :方格取數的同類題 - 傳紙條
https://www.acwing.com/problem/content/277/
題意
題目比較長,翻譯轉化之後大概就是在一個矩陣上找兩條從 (1, 1) 到 (m, n) 的路徑,中間沒有交點,且路徑數字和最大
分析
直觀上感覺這題跟上一題基本上一樣,除了要求不能相交。但是直覺告訴我們在上一題的答案中,不相交的路徑應該是較優的,因為矩陣上的數字總是非負的,直覺上繞開一下可以拿到更多。
嚴格證明參考 : https://www.acwing.com/solution/content/12389/
因此在上一題的程式碼上小改一下就可以了。
程式碼
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int f[N][N][N], w[N][N];
int main(){
int m, n;
cin >> m >> n;
for(int i = 1; i <= m; i ++)
for(int j = 1; j <= n; j ++)
cin >> w[i][j];
for(int k = 2; k <= m + n; k ++)
for(int i1 = 1; i1 < k; i1 ++)
for(int i2 = 1; i2 < k; i2 ++){
int j1 = k - i1, j2 = k - i2;
int t = w[i1][j1];
if(i1 != i2)t += w[i2][j2];
if(j1 >=1 && j1 <= n && j2 >= 1 && j2 <= n){
int& x = f[k][i1][i2];
x = max(x, f[k-1][i1][i2] + t);
x = max(x, f[k-1][i1-1][i2-1] + t);
x = max(x, f[k-1][i1-1][i2] + t);
x = max(x, f[k-1][i1][i2-1] + t);
}
}
cout << f[m + n][m][m] << endl;
return 0;
}