这道是NOI2005的原题吧
这道题乍一看,很容易设计出状态f[T][N][M],而且鉴于T有40000,K只有200,倾斜方向也是基于K的,因此把状态设计成f[K][N][M],即第K段时间滑到(N,M)位置的最大滑行距离。
设p[K]为第K段时间的持续长度,这样的话,根据倾斜方向不同,我们得到了四种转移方程。
f[K][N][M]=max(f[K-1][i][j]+dist)(dist<=p[K])
向上时:j=M,i>=N,dist=i-N
向下时:j=M,i<=N,dist=N-i
向左时:i=N,j>=M,dist=j-M
向右时:i=N,j<=M,dist=M-j
可以把K的那一维数组滚动掉,但是我很懒,没有这样做。
问题来了,这个转移方程虽然很有效,但是直接做会超时。我们发现,每次转移事实上都是在一行或者一列上进行取最大值的操作,这一操作不优化的话复杂度是O(M)或O(N),
把这一维单独拿出来(仅以向下滑动为例,其它的类比),就是f[N]=max(f[i]+N-i)=max(f[i]-i)+N(N-i<=p[K])
这样的话就符合了单调队列优化的基本模型,队列中存的是f[i]-i,当遇到障碍物时将队列清空。转移的复杂度降为O(1),最后的复杂度为O(KMN)
//By YY_More
#include<cstdio>
#include<iostream>
using namespace std;
char a[210][210];
int f[210][210][210];
int N,M,x,y,K,L,R,s,t,p[300],last[300],D[300];
void goup(int x){
for (int j=1;j<=M;j++){
L=0;R=-1;
for (int i=N;i>0;i--)
if (a[i][j]=='x') {L=0;R=-1;}
else{
while (L<=R&&f[x-1][D[R]][j]+D[R]<=f[x-1][i][j]+i) R--;
D[++R]=i;
while (D[L]-i>last[x]) L++;
f[x][i][j]=f[x-1][D[L]][j]+D[L]-i;
}
}
};
void godown(int x){
for (int j=1;j<=M;j++){
L=0;R=-1;
for (int i=1;i<=N;i++)
if (a[i][j]=='x') {L=0;R=-1;}
else{
while (L<=R&&f[x-1][D[R]][j]-D[R]<=f[x-1][i][j]-i) R--;
D[++R]=i;
while (i-D[L]>last[x]) L++;
f[x][i][j]=f[x-1][D[L]][j]+i-D[L];
}
}
};
void goleft(int x){
for (int i=1;i<=N;i++){
L=0;R=-1;
for (int j=M;j>0;j--)
if (a[i][j]=='x') {L=0;R=-1;}
else{
while (L<=R&&f[x-1][i][D[R]]+D[R]<=f[x-1][i][j]+j) R--;
D[++R]=j;
while (D[L]-j>last[x]) L++;
f[x][i][j]=f[x-1][i][D[L]]+D[L]-j;
}
}
};
void goright(int x){
for (int i=1;i<=N;i++){
L=0;R=-1;
for (int j=1;j<=M;j++)
if (a[i][j]=='x') {L=0;R=-1;}
else{
while(L<=R&&f[x-1][i][D[R]]-D[R]<=f[x-1][i][j]-j) R--;
D[++R]=j;
while (j-D[L]>last[x]) L++;
f[x][i][j]=f[x-1][i][D[L]]+j-D[L];
}
}
};
int main(){
scanf("%d%d%d%d%d",&N,&M,&x,&y,&K);
getchar();
for (int i=1;i<=N;i++){
for (int j=1;j<=M;j++)
a[i][j]=getchar();
getchar();
}
for (int i=1;i<=K;i++){
scanf("%d%d%d",&s,&t,&p[i]);
last[i]=t-s+1;
}
fill(&f[0][0][0],&f[K][N][M]+1,-50000);
f[0][x][y]=0;
for (int h=1;h<=K;h++)
switch(p[h]){
case 1:goup(h);break;
case 2:godown(h);break;
case 3:goleft(h);break;
case 4:goright(h);break;
}
int ans=0;
for (int i=1;i<=N;i++)
for (int j=1;j<=M;j++)
if (f[K][i][j]>ans) ans=f[K][i][j];
cout<<ans<<endl;
return 0;
}