題目大意:c++
約翰常常給產奶量高的奶牛發特殊津貼,因而很快奶牛們擁有了大筆不知該怎麼花的錢.爲此,約翰購置了 \(N(1 \le N \le 2000)\) 份美味的零食來賣給奶牛們.天天約翰售出一份零食.固然約翰但願這些零食所有售出後能獲得最大的收益.這些零食有如下這些有趣的特性:spa
\(V_i\) 是從盒子頂端往下的第i份零食的初始價值.約翰告訴了你全部零食的初始價值,並但願你能幫他計算一下,在這些零食全被賣出後,他最多能獲得多少錢.code
解題思路:遞歸
咱們定義狀態 \(f[L][R]\) 爲將區間 \([L,R]\) 依次去空能可以得到最多的錢。ci
那麼咱們能夠發現咱們的答案就是 \(f[1][n]\) ,那麼怎麼求解 \(f[1][n]\) 呢?先不急,聽我細細道來~it
假設咱們如今要求解 \(f[L][R]\) ,那麼咱們能夠發現,對於區間 \([L,R]\) ,咱們取走一份零食的方案只有兩種:for循環
對於區間 \([L,R]\) ,首先咱們要肯定咱們取走的零食(不管是方案一仍是方案二)是第幾份取走的零食?class
咱們能夠發現,\([L,R]\) 區間的左邊有 \(L-1\) 份零食在以前被取走了,右邊有 \(n-R\) 份零食在以前被取走了,因此咱們如今取的零食是第 \(L-1 + n-R + 1 = n+L-R\) 份。搜索
因此採用第一種方案可以得到的最多的錢是循環
\[f[L+1][R] + V_l \times (n+L-R)\]
採用第二種方案可以得到的最多的錢是
\[f[L][R-1] + V_R \times (n+L-R)\]
那咱們如今要求解的狀態 \(f[L][R]\) 應該是兩種方案的較大值,因此咱們能夠獲得最終的狀態轉移方程以下:
\[f[L][R] = \max(f[L+1][R] + V_l \times (n+L-R), f[L][R-1] + V_R \times (n+L-R))\]
固然,還須要注意的狀況是咱們的邊界條件,即:區間長度爲 \(1\) 時的狀況,此時,對於全部的區間 \([i,i]\) ,第 \(i\) 份零食都是最後取走的(即第 \(n\) 份被取走的),因此
\[f[i][i] = V_i \times n\]
基於上面的推導,咱們能夠經過記憶化搜索的形式實現咱們的主要代碼:
int dfs(int L, int R) { if (f[L][R]) // 記憶化操做 return f[L][R]; if (L == R) // 邊界條件 return V[L] * n; return f[L][R] = max(dfs(L+1, R)+V[L]*(n+L-R), dfs(L, R-1)+V[R]*(n+L-R)); }
記憶化搜索的思想仍是很是對應咱們人腦的思考方式的,咱們能夠發現,這個程序主要分爲三部分:
首先咱們的 dfs(L,R)
就是爲了返回 \(f[L][R]\),因此它:
使用記憶化搜索的完整代碼以下:
#include <bits/stdc++.h> using namespace std; const int maxn = 2020; int n, V[maxn], f[maxn][maxn]; int dfs(int L, int R) { if (f[L][R]) // 記憶化操做 return f[L][R]; if (L == R) // 邊界條件 return V[L] * n; return f[L][R] = max(dfs(L+1, R)+V[L]*(n+L-R), dfs(L, R-1)+V[R]*(n+L-R)); } int main() { cin >> n; for (int i = 1; i <= n; i ++) cin >> V[i]; cout << dfs(1, n) << endl; return 0; }
咱們也能夠採用通常形式來解決這個問題(通常形式和記憶化搜索形式的思路都是同樣的,只不過一個是直接for循環順着來,另外一個是遞歸着來,要注意區分和類比)。
咱們能夠發現,大區間(即區間長度較大的區間)對應的狀態都是經過小區間(即區間長度較小的區間)對應的狀態推導出來的,因此咱們只要從小到大遍歷區間長度,再遍歷區間左座標,計算對應狀態便可。
主要代碼以下:
for (int l = 1; l <= n; l ++) { // 從小到大遍歷區間長度l for (int i = 1; i+l-1 <= n; i ++) { // 遍歷區間左邊界i int j = i+l-1; // 經過左邊界i和區間長度l得到區間右邊界j if (l == 1) f[i][j] = V[i]*n; // 邊界條件直接返回結果 else f[i][j] = max(f[i+1][j] + V[i]*(n+i-j), f[i][j-1] + V[j]*(n+i-j)); // 不然,經過狀態轉移方程推導 } }
通常形式的完整實現代碼以下:
#include <bits/stdc++.h> using namespace std; const int maxn = 2020; int n, V[maxn], f[maxn][maxn]; int main() { cin >> n; for (int i = 1; i <= n; i ++) cin >> V[i]; for (int l = 1; l <= n; l ++) { // 從小到大遍歷區間長度l for (int i = 1; i+l-1 <= n; i ++) { // 遍歷區間左邊界i int j = i+l-1; // 經過左邊界i和區間長度l得到區間右邊界j if (l == 1) f[i][j] = V[i]*n; // 邊界條件直接返回結果 else f[i][j] = max(f[i+1][j] + V[i]*(n+i-j), f[i][j-1] + V[j]*(n+i-j)); // 不然,經過狀態轉移方程推導 } } cout << f[1][n] << endl; return 0; }