luogu P2858 [USACO06FEB]Treats for the Cows G/S
阿新 • • 發佈:2020-11-04
題目連結
題目概括:
給定一個序列v,每次可以從左端點處或右端點處取走一個數v[i],第a次取數可獲得的價值為v[i]*a,求把這個序列取完可獲得的最大價值
分析
要想獲得最大價值,肯定要讓大的數字後取。
做法
1.貪心(27分)
用雙指標列舉首和尾,看哪個小就先取哪個。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int N = 2010; int hh, tt, a, v[N], n, ans; int main() { scanf("%d", &n); hh = 1; for(tt = 1; tt <= n ; tt ++) scanf("%d", &v[tt]); tt = n; // cout << endl; while(hh <= tt) { if(v[hh] < v[tt]) { ans += v[hh] * ++a; hh ++; // cout << ans << endl; } else{ ans += v[tt] * ++a; tt --; // cout << ans << endl; } } cout << ans << endl; return 0; }
2.正解:區間DP
建議先看註釋,註釋很清楚。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int N = 2010; int n, v[N], f[N][N]; int main() { cin >> n; for(int i = 1; i <= n ; i ++) scanf("%d", &v[i]); for(int i = 1; i <= n ; i ++) f[i][i] = v[i] * n; for(int len = 2; len <= n ; len ++) { for(int i = 1; i <= n ; i ++) { int j = i + len - 1; f[i][j] = max(f[i+1][j]+v[i]*(n - len + 1), f[i][j-1]+v[j]*(n - len + 1)); } } /* f[i][j]表示區間[i, j]全都取完,可獲得的最大價值。 f[i][j]可以由兩個方向轉移過來 一個是1.f[i+1][j] 另一個2.f[i][j-1] 在如上兩種取法可獲得的最大價值中取max,再加上由1或2轉移到f[i][j]可獲得的價值,就是f[i][j] 邊界:當區間長度(j - i + 1) 為 1 時也就是i == j 時,可獲得的價值是v[i] * n 值得一提: 由1或2轉移到f[i][j]時,可獲得的價值是v[i]*a或v[j]*a,但是這裡的a用什麼來表示呢? 當轉移到的區間長度(len)為n時,a = 1; 當轉移到的區間長度(len)為n-1時,a = 2; ... ... 當轉移到的區間長度(len)為2時,a = n - 1; 當轉移到的區間長度(len)為1時,a = n 這時候我們可以發現: a + len 始終是等於n + 1的 ∵ a + len == n + 1 ∴ a == n - len + 1 程式碼實現: 根據區間dp的普遍寫法,先初始化邊界(i:1 to n, f[i][i] = v[i] * n) 然後列舉區間長度len 列舉左端點i 算出右端點j = i + len - 1 並轉移f[i][j] = max(f[i+1][j]+v[i]*(n - len +1), f[i][j-1]+v[j]*(n-len+1)) 最後的答案就是f[1][n](從第1個數到第n個數全部取完,可獲得的最大價值) */ cout << f[1][n] << endl; return 0; }
OI生涯中第一道區間DP
2020.11.4
寫於初中OI退役前第3天