1. 程式人生 > 實用技巧 >luogu P2858 [USACO06FEB]Treats for the Cows G/S

luogu P2858 [USACO06FEB]Treats for the Cows G/S

題目連結

題目概括:

  給定一個序列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天