2017普及第四题 跳房子 jump DP+二分

题目

跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一。

跳房子的游戏规则如下:

在地面上确定一个起点,然后在起点右侧画 n 个格子,这些格子都在同一条直线上。每个格子内有一个数字( 整数),表示到达这个格子能得到的分数。玩家第一次从起点开始向右跳, 跳到起点右侧的一个格子内。第二次再从当前位置继续向右跳,依此类推。规则规定:

玩家每次都必须跳到当前位置右侧的一个格子内。玩家可以在任意时刻结束游戏,获得的分数为曾经到达过的格子中的数字之和。

现在小 R 研发了一款弹跳机器人来参加这个游戏。但是这个机器人有一个非常严重的缺陷,它每次向右弹跳的距离只能为固定的 d。小 R 希望改进他的机器人,如果他花 g 个金币改进他的机器人,那么他的机器人灵活性就能增加 g, 但是需要注意的是,每次弹跳的距离至少为 1。 具体而言, 当g < d时, 他的机器人每次可以选择向右弹跳的距离为 d-g, d-g+1,d-g+2, …, d+g-2, d+g-1, d+g; 否则( 当g ≥ d时),他的机器人每次可以选择向右弹跳的距离为 1, 2, 3, …, d+g-2, d+g-1, d+g。

现在小 R 希望获得至少 k 分,请问他至少要花多少金币来改造他的机器人。

题解

因为数据很大,所以很容易想到二分答案。二分的每一次判定都是一次动态规划,方程如下:
f[i]=max(f[j])+w[i] (要求j点能跳到i点)
但是很显然超时,能得60分。
考试就想到了dp+二分,但是肯定会爆时间,不知道怎么优化。
dp+二分答案+一个优化的算法
观察动态方程,发现每一次都要找一个区间内的最大值来得出答案,这个区间满足能跳到i点,且f[i]单调递增,即如果后面的f[i]可行那么前面一定没有答案比它更优。
那么可以用单调队列来优化,也可以用堆。
单调队列+DP=绝配
那么问题来了——单调队列是什么?
翻了好几天的题解、百度百科,终于明白了。
单调队列是一个单调递增或递减的队列,队列先进先出,模板单调队列的区间内数量是固定的。简单来说,单调队列有两种操作:
1.把队头的多余数据去掉。在满足单调队列的题目中,如果一个点没法对当前的点贡献,则这个点对后面的点一定也是没有用的,这时候就要把这个点去掉,即头指针往后移一位
2.把一个点加进队列。每得出一个新的点都把它放进队列里,判断新点和队末点的大小,若新点优则把队末点去掉,即尾指针前移,继续这一步骤直至队末的点比新店优,这时把新点放在队尾
现在回到题目,发现一个f[i]得出后直接加进队列是错误的,因为f[i]只能跳到距离它d-g到d+g的点,距离它< d-g的点是转移不到的。
考虑维护一个now,表示当前最前面的一个没有加入单调队列的元素,当且仅当当前节点和它相差>=d-g的时候就让这个元素进队。这个进队在每次循环一个i的时候先做。
然后去除队头的多余状态
最后用队首的最大值得出f[i],并判断f[i]是否满足大于k分
时间复杂度(n log a[n])

代码

var
  n,d,m,i,j,k,l,r,mid,ans:longint;
  a,w:array[0..500000]of longint;
  f:array[0..500000]of longint;
  b,c:array[1..500000]of longint;

function try(mid:longint):boolean;
var
  i,j,h,t,now,s,k:longint;
begin
  fillchar(f,sizeof(f),200);
  s:=f[0];f[0]:=0;
  h:=1;t:=0;now:=-1;
  for i:=1 to n do
    begin
      for j:=now+1 to i-1 do
        begin
          if (a[j]+d-mid<=a[i])and(a[j]+d+mid>=a[i]) then
            begin
              while (t>=h)and(b[t]<=f[j]) do dec(t);
              inc(t);b[t]:=f[j];c[t]:=a[j];
              now:=j;
            end;
        end;
      while ((c[h]+d+mid<a[i])or(c[h]+d-mid>a[i]))and(h<=t) do inc(h);
      if (h<=t)and((c[h]+d+mid>=a[i])or(c[h]+d-mid<=a[i])) then f[i]:=b[h]+w[i];
      if f[i]>=m then exit(true);
    end;
  exit(false);
end;

begin
  readln(n,d,m);
  for i:=1 to n do
    begin
      readln(a[i],w[i]);
      if w[i]>0 then k:=k+w[i];
      if k>=m then k:=m;
    end;
  if k<m then begin writeln(-1);halt;end;
  l:=0;
  r:=a[n];
  while l<=r do
    begin
      mid:=(l+r) div 2;
      if try(mid) then begin ans:=mid;r:=mid-1;end else l:=mid+1;
    end;
  writeln(ans);
end.

我曾经想要把这鲜血呈献在众人面前,我曾经想要让英雄们的鲜红的旗帜在我们的头顶上飘扬。——罗素罗兰《名人传》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值