单调队列优化dp

洛谷p3800(单调队列优化DP

题目背景

据说在红雾异变时,博丽灵梦单身前往红魔馆,用十分强硬的手段将事件解决了。

然而当时灵梦在Power达到MAX之前,不具有上线收点的能力,所以她想要知道她能收集多少P点,然而这个问题她答不上来,于是她找到了学OI的你。

题目描述

可以把游戏界面理解成一个NM列的棋盘,有K个格子上有P点,其价值为val(i,j)

初始灵梦可以选择在第一行的任意一个格子出发,每秒她必须下移一格。

灵梦具有一个左右移动的速度T,可以使她每秒向左或右移动至多T,也可以不移动,并且不能折返。移动可视为瞬间完成,不经过路途上的点,只能获得目标格子的P点。

求最终她能获得的POWER值最大是多少?

输入输出格式

输入格式:

第一行四个数字,N,M,K,T

接下来K行每行3个数字x,y,v,代表第x行第y列有一个valvP点,数据保证一个格子上最多只有1P点。

输出格式:

一个数字

输入输出样例

输入样例#1 复制

3 3 4 1

1 1 3

1 2 1

2 2 3

3 3 3

输出样例#1 复制

9

说明

对于40%的测试点,1<=N,M,T,K<=200

对于100%的测试点,1<=N,M,T,K<=4000

v<=100N,M,K,T均为整数

题解:

看完题目,可发现这个题目像数塔,但是因为有速度,在一行可以左右移,所以可推出状态转移方程f[i,j]=max{f[i,j-k]~f[i,j+k],},所以复杂度为i*j*k,看范围,会超时。再观察题目,发现对于每个上一行,在一个区间内找一个最大值,且一格一格移动,联想到‘扫描’,可以用单调队列优化。先处理出k范围的队列(里面毕竟要先有数吧)(预处理)之后才能进行删头处理。再对第i行每个状态进行处理(1~j)可用滚动数组。此处单调队列与导弹拦截的优化不同,这是一个一个移的

var
 m1,s,n,m,i,k,j,tt,x,y,z,t,h,max,l:longint;
 ans:int64;
 q,f,dp,id:array[-10..100000]of longint;//q队列,id下标数组,dp滚动数组(协助存上一层数),f答案数组
 a:array[-1..4000,-1..4000]of longint;
begin
 readln(n,m,s,L);
  for i:=1 to s do
   begin
     readln(x,y,z);
     a[x,y]:=z;
   end;

   for i:=1 to m do  f[i]:=a[1,i];

    for i:=2 to n do
     begin
     h:=1;t:=1; q[1]:=f[1];id[1]:=1;
      for j:=2 to l do
      begin

             while (f[j]>q[t])and(h<=t)do dec(t);
                  inc(t); q[t]:=f[j];id[t]:=j;

      end;
       k:=l;//dp[1]=q[h]+a[1,i];
       for j:=1 to m do //you wrong here这里我调了好久,本来以为1已经计算过了,但不知道为什么没有,2改成1 就过了,所以dp尽量用记忆化
        begin
         if k+1<=m then
          begin
            inc(k);
            while (q[t]<f[k])and(h<=t) do dec(t);
            inc(t); q[t]:=f[k];id[t]:=k;
          end;
             begin  while id[h]<j-l do inc(h);
                   dp[j]:=q[h]+a[i,j];
             end;
        end;
     for j:=1 to m do
      begin
        f[j]:=dp[j];
        if max<f[j] then max:=f[j];
      end;
    end;

     writeln(max);
end.

 

posted @ 2018-08-13 21:41  jiangyihui  阅读(959)  评论(0编辑  收藏  举报