POJ - 3186(Treats for the Cows)

题目大意

给定右N个数字的序列,v1v2…vn,每次可以取出最左端或者最右端的数字,将序列所有数字取出。假设第 i 次取出数字为 vi ,则可获得 i*vi 的价值,问如何规划取数顺序,才能使价值和最大,输出最大价值和。

提示翻译:
按照这种下标顺序取数: 1, 5, 2, 3, 4
取出的数按顺序为:1, 2, 3, 1, 5
最大总价值和:1x1 + 2x2 + 3x3 + 4x1 + 5x5 = 43.

解题思路

由于每次只能取最左端或者最右端的数字,所有对于一个序列 vi…vj,只能先取 vi 或者 vj后,才能考虑其中间的数字。不妨做个假设,如果知道了取出 vi+1…vj 和 vi…vj-1这两个子序列中序所有数字的最大价值,那么我们就可以求出 vi…vj 序列的最大价值(求解在下述状态转移中)。由此,我们可以确定大致思路就是将区间划分为小区间,直至只有一个元素时,求出该区间的最大值(对于第 i 个元素可能是第1~n次取,但是当前只考虑一个元素时显然是第n次取最合适,即价值为value[i] * n),然后不断合并扩大区间取区间最大值,直至区间覆盖整个序列。
1.状态表示:dp[i][j]表示取出i~j区间数字的所有方案中最大价值是多少
2.动态转移:假设第 k 次取出数字构成了朴素区间 vi…vj,则由两种可能
–>a.第 k 次取 vi,即dp[i+1][j] + k * value[i] (value[i]表示vi)
–>b.第 k 次取 vj,即dp[i][j-1] + k * value[j]
现在只需要考虑 k 如何得出,如果仔细思考上述分析不难发现,其实我们在求解的过程中并不是从1~n这样选择,而是先求出了第n次可能取出的数字,然后计算第n次和n-1次的情况…。
k = n - len + 1 (len表示当前区间长度,在此之前已经选择了 len-1 个数字,本次选择就是倒数第len次选择,即正序第n-len+1次选择)
dp[i][j] = max{dp[i+1][j] + (n - len + 1) * value[i], dp[i][j-1] + (n - len + 1) * value[j]}

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN = 2005;
int value[MAXN];
int dp[MAXN][MAXN];     //dp[i][j]表示取出i~j区间所有数字所得最大价值
int main() {
    int n;
    while(cin >> n) {
        memset(dp, 0, sizeof(dp));
        for(int i = 1; i <= n; i++) {
            cin >> value[i];
        }
        for(int l = 1; l <= n; l++) {       //l表示区间长度
            for(int i = 1, j = (i+l-1); j <= n; i++, j = (i+l-1)) {     
                //i~j表示区间左右端点,i表示区间左端点,j表示区间右端点
                //即j = i + l - 1
                dp[i][j] = max(dp[i+1][j] + (n - l + 1) * value[i], dp[i][j-1] + (n - l + 1) * value[j]);
            }
        }
        cout << dp[1][n] << endl;
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值