题目
跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一。
跳房子的游戏规则如下:
在地面上确定一个起点,然后在起点右侧画 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.