传送门
一个很显然的O(nmT)的做法就是令f[t][i][j]表示时刻t时,钢琴位于(i,j)处时,从时刻1到t的最长滑行路程。很容易得到DP方程
f[t][i][j]=maxf[t−1][i][j],f[t−1][ilast][jlast]+1
这样做显然太慢,由于钢琴的移动方向是在一段一段的时间内是相同的,因此可以考虑成段DP。令f[t][i][j]表示第t段时间时,钢琴位于(i,j)处时,从第1段时间到第t段时间的最长滑行路程。
f[t][i][j]=maxf[t−1][ilast][jlast]+dist(ilast,jlast),(i,j)
下面举一个简单例子,当前这段时间内移动方向都是朝右,来讲讲如何利用单调队列优化
可以列出DP方程
f[t][i][j]=maxf[t−1][i][j']+(j−j')=j+maxf[t−1][i][j']−j',j−j'≤startt−endt+1
第t段移动方向对应于时间段[startt,endt]
j是个常量,因此我们就是要找到最大的max{f[t−1][i][j′]−j′},我们可以维护一个单调队列,保存t−1时刻的所有可行状态的f值与j′,保证j′单调递增,f值单调递减,在dp到第t段时,首先枚举行i,然后从左到右来枚举j,并时刻保证队首的j′满足限制条件j−j′≤startt−endt+1,这样的单调队列的队首显然是最优的,用此时的队首的f[t−1][i][j′]来更新答案。
其他三个方向的DP过程也类似,这样做最终的时间复杂度是O(nmk)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define N 210
using namespace std;
struct abcd{
int x,y;
}q[N];
char a[N][N];
int f[N][N],g[N][N];
int n,m,k,ans,x,y,z,h,r;
int dis(abcd a,abcd b){return abs(a.x-b.x)+abs(a.y-b.y);}
int ins(abcd x){
while (h<r&&dis(x,q[r])<=f[x.x][x.y]-f[q[r].x][q[r].y]) r--;
q[++r]=x;
}
int get(abcd x,int len){
while (h<r&&dis(q[h+1],x)>len) h++;
if (r==h) return 0xefefefef;
return f[q[h+1].x][q[h+1].y]+dis(x,q[h+1]);
}
void up(int len){
for (int j=1;j<=n;j++){
r=h=0;
for (int i=m;i;i--)
if (a[i][j]=='.'){
abcd p;
p.x=i;
p.y=j;
g[i][j]=max(f[i][j],get(p,len));
ins(p);
}
else h=r=0;
}
memcpy(f,g,sizeof(f));
}
void down(int len){
for (int j=1;j<=n;j++){
r=h=0;
for (int i=1;i<=m;i++)
if (a[i][j]=='.'){
abcd p;
p.x=i;
p.y=j;
g[i][j]=max(f[i][j],get(p,len));
ins(p);
}
else h=r=0;
}
memcpy(f,g,sizeof(f));
}
void left(int len){
for (int i=1;i<=m;i++){
r=h=0;
for (int j=n;j;j--)
if (a[i][j]=='.'){
abcd p;
p.x=i;
p.y=j;
g[i][j]=max(f[i][j],get(p,len));
ins(p);
}
else h=r=0;
}
memcpy(f,g,sizeof(f));
}
void right(int len){
for (int i=1;i<=m;i++){
r=h=0;
for (int j=1;j<=n;j++)
if (a[i][j]=='.'){
abcd p;
p.x=i;
p.y=j;
g[i][j]=max(f[i][j],get(p,len));
ins(p);
}
else h=r=0;
}
memcpy(f,g,sizeof(f));
}
int main(){
scanf("%d%d%d%d%d",&m,&n,&x,&y,&k);
for (int i=1;i<=m;i++) scanf("%s",a[i]+1);
memset(f,0xef,sizeof(f));
memset(g,0xef,sizeof(g));
f[x][y]=0;
for (int i=1;i<=k;i++){
scanf("%d%d%d",&x,&y,&z);
if (z==1) up(y-x+1);
if (z==2) down(y-x+1);
if (z==3) left(y-x+1);
if (z==4) right(y-x+1);
}
for (int i=1;i<=m;i++)
for (int j=1;j<=n;j++) ans=max(ans,f[i][j]);
printf("%d",ans);
}