【USACO OPEN 10】hop

奶牛们正在回味童年,玩一个类似跳格子的游戏,在这个游戏里,奶牛们在草地上画了一行N个格子,(3 <=N <= 250,000),编号为1..N。


  就像任何一个好游戏一样,这样的跳格子游戏也有奖励!第i个格子标有一个数字V_i(-2,000,000,000 <=V_i <= 2,000,000,000)表示这个格子的钱。奶牛们想看看最后谁能得到最多的钱。
  
规则很简单:
  * 每个奶牛从0号格子出发。(0号格子在1号之前,那里没钱)  *她向N号格子进行一系列的跳跃(也可以不跳),每次她跳到的格子最多可以和前一个落脚的格子差K格(1 <= K <= N)(比方说,当前在1号格,K=2, 可以跳到2号和3号格子)
  *在任何时候,她都可以选择回头往0号格子跳,直到跳到0号格子。另外,除了以上规则之外,
   回头跳的时候还有两条规则:    
    *不可以跳到之前停留的格子。
    *除了0号格子之外,她在回来的时候,停留的格子必须是恰巧过去的时候停留的某个格子的前一格(当然,也可以跳过某些过去时候停留的格子)。简单点说,如果i号格子是回来 停留的格子,i+1号就必须是过去停留的格子,如果i+1号格子是过去停留的格子,i号格子 不一定要是回来停留的格子。(如果这里不太清楚的可以去看英文原文)

  她得到的钱就是所有停留过的格子中的数字的和,请你求出最多奶牛可以得到的钱数。

  在样例中,K=2,一行5个格子。  
  
  
  
  一个合法的序列Bessie可以选择的是0[0], 1[0], 3[2], 2[1], 0[0]。(括号里的数表示钱数)
这样,可以得到的钱数为0+0+2+1+0 = 3。

  如果Bessie选择一个序列开头为0, 1, 2, 3, ...,那么她就没办法跳回去了,因为她没办法再跳到一个之前没跳过的格子。
  序列0[0], 2[1], 4[-3], 5[4], 3[2], 1[0], 0[0]是最大化钱数的序列之一,最后的钱数为(0+1-3+4+2+0 = 4).

思考过程:这道题很奇怪的有两个过程,第一个过程是去的过程,第二个过程是回来的过程,而且回来时候经过的每个格子都是去时格子的前驱;

我先考虑的是设f[i]表示去时到达的最右边的格子是i时的最大得分,但是尝试之后发现这是无法转移的,因为我不知道回来的时候会不会经过它前面的那个格子,经过不经过两种情况都需要考虑,再加上每次最多跳k个格子这样的限制,设f[i]这样的状态很显然是不可做的;

到这里就有点进了死胡同的感觉,于是去看了下题解,然后回来继续做。。。

看完了题解就感觉豁然开朗了,我们可以设f[i]表示回来时第i个格子是第一个跳的格子的可得到的最大得分;这么设置,一是当前状态对以后状态没有了后效性,二是便于优化;

想这道题时候其实隐隐约约把握到了这道题需要从后往前想的思路,但是没有付诸实际,所以最后只好去看题解;

代码上有些细节需要注意;

代码:

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #include<cstdlib>
 5 #include<algorithm>
 6 #include<iomanip>
 7 #include<map>
 8 #include<set>
 9 #include<vector>
10 #include<ctime>
11 #include<cmath>
12 #define LL long long
13 using namespace std;
14 #define LL long long
15 #define up(i,j,n) for(int i=(j);(i)<=(n);(i)++)
16 const int maxn=502000;
17 int n,k;
18 int a[maxn];
19 LL f[maxn],sum[maxn];
20 int q[maxn],head=1,tail=1,g[maxn];
21 void print(LL x){printf("%I64d\n",x);exit(0);}
22 inline LL max(LL x,LL y){return x>y?x:y;}
23 inline LL min(LL x,LL y){return x>y?y:x;}
24 int main(){
25     scanf("%d%d",&n,&k);n++;
26     up(i,2,n){scanf("%d",&a[i]);sum[i]=sum[i-1]+max(0,a[i]);}
27     if(k==1)print(max(a[1],0));
28     LL ans=0;
29     for(int i=n+1;i<=n+k;i++)sum[i]=sum[i-1];
30     up(i,1,n){
31         while(i-q[head]>k&&head<=tail)head++;
32         f[i]=f[q[head]]-sum[q[head]+1]+sum[i-1]+a[i]+a[i+1];
33         while(f[q[tail]]-sum[q[tail]+1]<=f[i-1]-sum[i]&&tail>=head)tail--;
34         q[++tail]=i-1;f[1]=max(0LL,f[1]);
35         ans=max(ans,f[i]+sum[i+k]-sum[i+1]);
36     }
37     print(ans);
38     return 0;
39 }

时间: 2024-10-08 10:29:02

【USACO OPEN 10】hop的相关文章

【USACO 1.1】你的飞碟在这儿

[问题描述] 一个众所周知的事实,在每一慧星后面是一个不明飞行物UFO. 这些不明飞行物时常来收集来自在地球上忠诚的支持者. 不幸地,他们的空间在每次旅行只能带上一群支持者. 他们要做的是用一种聪明的方案让每一个团体人被慧星带走.他们为每个慧星起了一个名字,通过这些名字来决定一个团体是不是特定的慧星带走. 那个相配方案的细节在下面被给出: 你的工作要写一个程序来通过团体的名字和彗星的名字来决定一个组是否应该与在那一颗慧星后面的不明飞行物搭配. 团体的名字和慧星的名字都以下列各项方式转换成一个数字

【USACO 1.2】贪婪的送礼者

[题目描述] 对于一群(NP个)要互送礼物的朋友,GY要确定每个人送出的钱比收到的多多少. 在这一个问题中,每个人都准备了一些钱来送礼物,而这些钱将会被平均分给那些将收到他的礼物的人. 然而,在任何一群朋友中,有些人将送出较多的礼物(可能是因为有较多的朋友),有些人有准备了较多的钱. 给出一群朋友,没有人的名字会长于 14 字符,给出每个人将花在送礼上的钱,和将收到他的礼物的人的列表, 请确定每个人收到的比送出的钱多的数目. [输入格式] 第 1 行: 人数NP,2<= NP<=10 第 2

【USACO 1.3】黑色星期五

[问题描述] 13号又是一个星期五.13号在星期五比在其他日子少吗?为了回答这个问题,写一个程序,要求计算每个月的十三号落在周一到周日的次数.给出N年的一个周期,要求计算1900年1月1日至1900+N-1年12月31日中十三号落在周一到周日的次数,N为正整数且不大于400. 这里有一些你要知道的: 1.1900年1月1日是星期一.2.4,6,11和9月有30天.其他月份除了2月都有31天.闰年2月有29天,平年2月有28天.3.年份可以被4整除的为闰年(1992=4*498 所以 1992年是

【C++基础 10】四种cast转换的区别

简介 (1)c风格的转换 (T)expression; (2)c++风格的四种转换 static_cast<T>(expression); dynamic_cast<T>(expression); reinterpret_cast<T>(expression); const_cast<T>(expression); 1. c风格转换 int a = 1; double d = (double)a;//c风格转换 一般许多书本会建议使用c++提供的四种类型转换

【USACO 2012 Open】Running Laps(树状数组)

53 奶牛赛跑 约翰有 N 头奶牛,他为这些奶牛准备了一个周长为 C 的环形跑牛场.所有奶牛从起点同时起跑,奶牛在比赛中总是以匀速前进的,第 i 头牛的速度为 Vi.只要有一头奶牛跑完 L 圈之后,比赛就立即结束了.有时候,跑得快的奶牛可以比跑得慢的奶牛多绕赛场几圈,从而在一些时刻超过慢的奶牛.这就是最令观众激动的套圈事件了.请问在整个比赛过程中,套圈事件一共会发生多少次呢?输入格式? 第一行:三个整数 N, L 和 C, 1 ≤ N ≤ 105 , 1 ≤ L ≤ 25000 , 1 ≤ C

【BZOJ】【1986】【USACO 2004 Dec】/【POJ】【2373】划区灌溉

DP/单调队列优化 首先不考虑奶牛的喜欢区间,dp方程当然是比较显然的:$ f[i]=min(f[k])+1,i-2*b \leq k \leq i-2*a $  当然这里的$i$和$k$都是偶数啦~这个应该很好理解吧……每次喷灌的都是一个偶数长度的区间嘛…… 那么加上奶牛的喜欢区间的话,只需这样:当$ i>cow[j].x $时,令$ i=cow[j].y , j++$ 也就是说中间的位置全部不考虑放喷灌器. 显然我们对于每个节点的 k 是可以用单调队列维护的!嗯看到这里的同学可以先自己试着去

【C++ Primer | 10】再探迭代器

反向迭代器 1. 测试代码: 1 #include<iostream> 2 #include<vector> 3 #include<iterator> 4 using namespace std; 5 6 int main() 7 { 8 vector<int> vec = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 9 for (auto r_iter = vec.crbegin(); r_iter != vec.crend();

【USACO 2.1】Hamming Codes

/* TASK: hamming LANG: C++ URL:http://train.usaco.org/usacoprob2?a=5FomsUyB0cP&S=hamming SOLVE: 找粗一个值最小的n个元素的集合,每个元素都是不超过m位二进制的数,且两两之间二进制位不同的位不小于d. dfs,枚举每一个数,枚举范围:(前一个数,1<<m),每次进入dfs都判断一下当前集合是否满足两两距离不小于d. */ #include<cstdio> int n,m,d; in

【USACO 2.2】Preface Numbering (找规律)

求 1-n 的所有罗马数字表达中,出现过的每个字母的个数. 分别对每个数的罗马表达式计算每个字母个数. 对于十进制的每一位,都是一样的规则,只是代表的字母不同. 于是我们从最后一位往前考虑,当前位由字母 s[i] 代表 1,字母 s[i+1] 代表 5,s[i+2] 代表 10(在下一次代表1). 每一位考虑完 i+=2; num[i] 为当前位为i对应的 s[i] 的个数,当前位为 4~8 时,s[i+1] 出现 1 次,当前位为 9 时,s[i+2] 出现一次. http://train.u