# APCS實作題2020年10月第3題:勇者修煉 > 日期:2024年7月11日 > 作者:王一哲 > [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=f314) ## 題目 ### 問題描述 輸入為 $m \times n$ 大小的的陣列,每一格是一個介於 $-100$ 與 $100$ 之間的整數,表示經過這格可以累積的經驗值。你可以從最上面一排任何一個位置開始,在最下面一排任何一個位置結束。過程中每一步可以選擇往左、往右或往下走,但不能走回已經經過的位置。請你算出最多可以獲得的經驗值總和(可能是負數)。 <br /> ### 輸入說明 第一行有兩個正整數 $m, n ~(1 \leq m \leq 50,~ 1 \leq n \leq 10000)$。 接下來 $m$ 行,每行包含 $n$ 個整數。第 $i$ 行的第 $j$ 個數字表示在 $(i, j)$ 位置可以得到的經驗值。 配分 - 20%:$n \leq 100,~ m=1$ - 30%:$n \leq 100$ - 50%:$n \leq 10000$ <br /> ### 輸出格式 輸出可以獲得的最多經驗值總和。 <br /> ### 範例輸入 ``` 1 5 2 1 4 -7 4 ``` ### 範例輸出 ``` 7 ``` <br /> ## Python 程式碼 ### 寫法1,用 $m \times n$ 串列儲存走到位置 (r, c) 的最大經驗值 比較容易理解的寫法,會使用比較多的記憶體。費時最久約為 0.9 s,使用記憶體最多約為 17.6 MB,通過測試。 ```python= m, n = map(int, input().split()) # 讀取地圖列數 m、欄數 n dp = [[0]*n for _ in range(m)] # dp[r][c],走到 (r, c) 位置的最大經驗值 lmax = [0]*n # 由左向右走到第 c 欄的最大經驗值 rmax = [0]*n # 由右向左走到第 c 欄的最大經驗值 pos = list(map(int, input().split())) # 一次讀一列地圖資料 """ 先處理第 0 列 """ # 由左向右走 lmax[0] = pos[0] # 先處理位置 0 for c in range(1, n): # 處理 1 ~ n-1,只能往右走 lmax[c] = max(lmax[c-1], 0) + pos[c] # 取 lmax[c-1], 0 較大者加上 pos[c] # 由右向左走 rmax[n-1] = pos[n-1] # 先處理位置 n-1 for c in range(n-2, -1, -1): # 處理 n-2 ~ 0,只能往左走 rmax[c] = max(rmax[c+1], 0) + pos[c] # 取 rmax[c+1], 0 較大者加上 pos[c] # 取出 lmax[c], rmax[c] 較大者存到 dp[0][c] for c in range(n): dp[0][c] = max(lmax[c], rmax[c]) """ 再處理第 1 ~ m-1 列 """ for r in range(1, m): # 重複 m-1 次 pos = list(map(int, input().split())) # 一次讀一列地圖資料 # 由左向右走 lmax[0] = dp[r-1][0] + pos[0] # 先處理位置 0,只能由上往下走 for c in range(1, n): # 處理 1 ~ n-1,取往下走、往右走總合較大者 lmax[c] = max(dp[r-1][c], lmax[c-1]) + pos[c] # 由右向左走 rmax[n-1] = dp[r-1][n-1] + pos[n-1] # 先處理位置 0,只能由上往下走 for c in range(n-2, -1, -1): # 處理 n-2 ~ 0,取往下走、往左走總合較大者 rmax[c] = max(dp[r-1][c], rmax[c+1]) + pos[c] # 取出 lmax[c], rmax[c] 較大者存到 dp[r][c] for c in range(n): dp[r][c] = max(lmax[c], rmax[c]) """ end of for loop """ ans = max(dp[m-1]) # 取最大值 print(ans) # 印出答案 ``` <br /><br /> ### 寫法2,用二維串列記錄每一格最大、向右、向左走的經驗值 由於 dp 串列裡的值會不斷蓋掉,改成這一列對應的值,比較難讀懂程式碼。費時最久約為 0.9 s,使用記憶體最多約為 8.3 MB,通過測試。 ```python= m, n = map(int, input().split()) # 讀取地圖列數 m、欄數 n # dp[c],以第c欄作為終點的最大經驗值,資料為 [兩種走法的最大值, 由左向右走, 由右向左走] dp = [[0]*3 for _ in range(n)] """ 主要的解題過程 """ for r in range(m): # 重複 m 次 pos = list(map(int, input().split())) # 一次讀一列地圖資料 # 由左向右走 dp[0][1] = dp[0][0] + pos[0] # 先處理位置 0,只能由上往下走 for c in range(1, n): # 處理 1 ~ n-1,取往下走、往右走總合較大者 dp[c][1] = max(dp[c][0] + pos[c], dp[c-1][1] + pos[c]) # 由右向左走 dp[n-1][2] = dp[n-1][0] + pos[n-1] # 先處理位置 n-1,只能由上往下走 for c in range(n-2, -1, -1): # 處理 (r, n-2) ~ (r, 0),取往下走、往左走總合較大者 dp[c][2] = max(dp[c][0] + pos[c], dp[c+1][2] + pos[c]) # 取出 dp[c][1], dp[c][2] 的最大值存到 dp[c][0] for d in dp: d[0] = max(d[1], d[2]) """ end of for loop """ ans = max([d[0] for d in dp]) # 取最大值 print(ans) # 印出答案 ``` <br /><br /> ## C++ 程式碼 ### 寫法1,用 $m \times n$ 陣列儲存走到位置 (r, c) 的最大經驗值 比較容易理解的寫法,會使用比較多的記憶體。費時最久約為 40 ms,使用記憶體最多約為 1.6 MB,通過測試。 ```cpp= #include <iostream> #include <algorithm> #include <vector> using namespace std; int main() { ios::sync_with_stdio(0); cin.tie(0); // 加速 int m, n; cin >> m >> n; // 讀取地圖列數 m、欄數 n vector<vector<int>> dp (m, vector<int> (n, 0)); // dp[r][c],走到 (r, c) 位置的最大經驗值 vector<int> lmax (n, 0), rmax (n, 0), pos (n, 0); // 分別為由左向右、由右向左走到第 c 欄的最大經驗值,地圖經驗值 for(int c=0; c<n; c++) cin >> pos[c]; // 讀取一列地圖資料 /* 先處理第 0 列 */ // 由左向右走 lmax[0] = pos[0]; // 先處理位置 0 for(int c=1; c<n; c++) // 處理 1 ~ n-1,只能往右走 lmax[c] = max(lmax[c-1], 0) + pos[c]; // 取 lmax[c-1], 0 較大者加上 pos[c] // 由右向左走 rmax[n-1] = pos[n-1]; // 先處理位置 n-1 for(int c=n-2; c>=0; c--) // 處理 n-2 ~ 0,只能往左走 rmax[c] = max(rmax[c+1], 0) + pos[c]; // 取 rmax[c+1], 0 較大者加上 pos[c] // 取出 lmax[c], rmax[c] 較大者存到 dp[0][c] for(int c=0; c<n; c++) dp[0][c] = max(lmax[c], rmax[c]); /* 再處理第 1 ~ m-1 列 */ for(int r=1; r<m; r++) { // 重複 m-1 次 for(int c=0; c<n; c++) cin >> pos[c]; // 讀取一列地圖資料 // 由左向右走 lmax[0] = dp[r-1][0] + pos[0]; // 先處理位置 0,只能由上往下走 for(int c=1; c<n; c++) // 處理 1 ~ n-1,取往下走、往右走總合較大者 lmax[c] = max(dp[r-1][c], lmax[c-1]) + pos[c]; // 由右向左走 rmax[n-1] = dp[r-1][n-1] + pos[n-1]; // 先處理位置 0,只能由上往下走 for(int c=n-2; c>=0; c--) // 處理 n-2 ~ 0,取往下走、往左走總合較大者 rmax[c] = max(dp[r-1][c], rmax[c+1]) + pos[c]; // 取出 lmax[c], rmax[c] 較大者存到 dp[r][c] for(int c=0; c<n; c++) dp[r][c] = max(lmax[c], rmax[c]); } int ans = *max_element(dp[m-1].begin(), dp[m-1].end()); // 取最大值 cout << ans << "\n"; // 印出答案 return 0; } ``` <br /><br /> ### 寫法2,用二維陣列記錄每一格最大、向右、向左走的經驗值 由於 dp 串列裡的值會不斷蓋掉,改成這一列對應的值,比較難讀懂程式碼。費時最久約為 42 ms,使用記憶體最多約為 772 kB,通過測試。 ```cpp= #include <iostream> #include <algorithm> #include <vector> using namespace std; int main() { ios::sync_with_stdio(0); cin.tie(0); // 加速 int m, n; cin >> m >> n; // 讀取地圖列數 m、欄數 n // dp[c],以第c欄作為終點的最大經驗值,資料為 [兩種走法的最大值, 由左向右走, 由右向左走] vector<int> pos (n, 0); vector<vector<int>> dp (n, vector<int> (3, 0)); /* 主要的解題過程 */ for(int r=0; r<m; r++) { // 重複 m 次 for(int c=0; c<n; c++) cin >> pos[c]; // 一次讀一列地圖資料 // 由左向右走 dp[0][1] = dp[0][0] + pos[0]; // 先處理位置 0,只能由上往下走 for(int c=1; c<n; c++) // 處理 1 ~ n-1,取往下走、往右走總合較大者 dp[c][1] = max(dp[c][0] + pos[c], dp[c-1][1] + pos[c]); // 由右向左走 dp[n-1][2] = dp[n-1][0] + pos[n-1]; // 先處理位置 n-1,只能由上往下走 for(int c=n-2; c>=0; c--) // 處理 (r, n-2) ~ (r, 0),取往下走、往左走總合較大者 dp[c][2] = max(dp[c][0] + pos[c], dp[c+1][2] + pos[c]); // 取出 dp[c][1], dp[c][2] 的最大值存到 dp[c][0] for(int c=0; c<n; c++) dp[c][0] = max(dp[c][1], dp[c][2]); } // 取最大值 int ans = dp[0][0]; for(int c=1; c<n; c++) ans = max(ans, dp[c][0]); cout << ans << "\n"; // 印出答案 return 0; } ``` <br /><br /> --- ###### tags:`APCS`、`Python`、`C++`