洛谷p3800(单调队列优化DP)
题目背景
据说在红雾异变时,博丽灵梦单身前往红魔馆,用十分强硬的手段将事件解决了。
然而当时灵梦在Power达到MAX之前,不具有“上线收点”的能力,所以她想要知道她能收集多少P点,然而这个问题她答不上来,于是她找到了学OI的你。
题目描述
可以把游戏界面理解成一个N行M列的棋盘,有K个格子上有P点,其价值为val(i,j)
初始灵梦可以选择在第一行的任意一个格子出发,每秒她必须下移一格。
灵梦具有一个左右移动的速度T,可以使她每秒向左或右移动至多T格,也可以不移动,并且不能折返。移动可视为瞬间完成,不经过路途上的点,只能获得目标格子的P点。
求最终她能获得的POWER值最大是多少?
输入输出格式
输入格式:
第一行四个数字,N,M,K,T
接下来K行每行3个数字x,y,v,代表第x行第y列有一个val为v的P点,数据保证一个格子上最多只有1个P点。
输出格式:
一个数字
输入输出样例
输入样例#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<=100,N,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.