这道是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; }