【USACO OPEN 10】hop

Description

奶牛们正在回味童年,玩一个类似跳格子的游戏,在这个游戏里,奶牛们在草地上画了一行N个格子,(3 <=N <= 250,000),编号为1..N。
就像任何一个好游戏一样,这样的跳格子游戏也有奖励!第i个格子标有一个数字V_i(-2,000,000,000 <=V_i <= 2,000,000,000)表示这个格子的钱。奶牛们想看看最后谁能得到最多的钱。
规则很简单:
1 每个奶牛从0号格子出发。(0号格子在1号之前,那里没钱)

2 在任何时候,她都可以选择回头往0号格子跳,直到跳到0号格子。

3不可以跳到之前停留的格子。
除了0号格子之外,她在回来的时候,停留的格子必须是恰巧过去的时候停留的某个格子的前一格(当然,也可以跳过某些过去时候停留的格子)。简单点说,如果i号格子是回来停留的格子,i+1号就必须是过去停留的格子,但如果i+1号格子是过去停留的格子,i号格子不一定要是回来停留的格子。
她得到的钱就是所有停留过的格子中的数字的和,请你求出最多奶牛可以得到的钱数。
注意:必须跳回0点。

Solution

这种题目一眼就知道是DP了。
关键是怎么设置状态。
我们设Fi 表示把i作为往回跳的第一步的最大收益
我来解释一下这个状态
就是上一步跳到i+1,此时决定回头,毅然决然跳到i,然后继续往回跳的最大收益【i+1之后的不跳哦】
假设我们下一步跳到j
状态转移为

Fi=max(Fj+Sumi1Sumj+1+vi+vi+1),iK<=j<=i2

Sum[i]表示从0到i的所有正数之和。
我们是这样跳的:
从j+1开始慢慢跳,把i之前的j+1之后的正数全部取完,然后跳到i+1,然后跳回i,然后跳回j。
这就是Fi
然而F并不是答案。因为i+1之后我们并没有跳,我们应该要跳。为什么我们的状态不设呢?因为我们不知道他跳了哪些点,可能会对后续的状态有影响,这是绝对不允许的。
所以我们的ans应该为

ans=max(ans,Fi+Sumi+KSumi+1)

即把后面K步的正数全部取完再一步跳回i。

解释完以后,F怎么求?
状态可以变为

Fi=max(FjSumj+1)+Sumi1+vi+vi+1,iK<=j<=i2

单调队列或者堆乱搞一下就好了。
注意边界。
Good Luck。

#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 501000;

int que[N],v[N],n,K,head,tail;
long long sum[N],f[N],ans,g[N];

int main()
{
    freopen("0.in" , "r" , stdin);freopen("0.out" , "w" , stdout);

    scanf("%d%d" , &n , &K);n ++;
    for (int i = 2 ; i <= n ; i ++)
    {
        scanf("%d" , &v[i]);
        sum[i] = sum[i - 1] + v[i] * (v[i] > 0);
    }
    for (int i = n + 1 ; i <= n + K ; i ++) sum[i] = sum[i - 1];

    head = 1 , tail = 1;
    for (int i = 1 ; i <= n ; i ++)
    {
        while (que[head] < i - K && head <= tail) head ++;
        f[i] = (long long)v[i] + v[i + 1] + sum[i - 1] + g[que[head]];
        while (head <= tail && g[que[tail]] <= g[i - 1]) tail --;
        que[++ tail] = i - 1; f[1] = max(f[1] , 0LL);
        ans = max(ans , f[i] + sum[i + K] - sum[i + 1]);
        g[i] = f[i] - sum[i + 1];
    }

    printf("%I64d\n" , ans);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值