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天

posted @ 2020-11-04 11:59  Lvlb2333  阅读(106)  评论(0)    收藏  举报