Ural(Timus) 1119. Metro
DP(网格DP+空间压缩)
题意:给你一个网格,最左下角下标为(0,0),最右上角坐标(N,M),每个格子都是100*100的正方形,从当前点可以向上下左右4个方向移动,如果恰好当前点有对角线,还可以向对角线移动。另外给你K个坐标,表示那些方格有对角线,这些坐标是方格的右上角的坐标,好像(50,50),其实就是(49,49)和(50,50)有斜线连接,(34,67)其实就是(33,66)和(34,67)有连接。现在问你从(0,0)到(N,M)的最短距离,结果四舍五入为整数
DP思想还是很简单的,但是要压缩空间否则会MLE。先说说一开始的思路
1.首先,从当前出发,不可能向左或者向下或者向左下走,只可能向上,向右或者向右上走,所以设dp[i][j],表示在坐标(i,j)到(N,M)的最短路。那么初始化dp[N][M]=0;其余点均为INF,我们要的状态是dp[0][0]。然后用记忆化搜索,实现这样需要一个1000*1000的dp数组.
2.回顾上面说的,不可能向左,下,左下走,其实是什么,只能在当前这一行走,或者走向下一行。所以其实我们只需要知道一行的信息——知道了前一行每个点的最短路估计值,用他们来更新这一行的最短路估计值。这样我们就可以采用递推的方式来实现
用一个dp[2][1000]的数组,dp[1][j]表示(0,0)到当前这一行,列为j的点的最短路,dp[0][j]表示当前这一行的前一行。可见“当前行”和“前一行”是一个相对的概念,在不断替换着。
那么状态转移方程也是很容易写出的
dp[1][j]=min{ dp[1][j-1]+100 , dp[0][j]+100 , dp[0][j-1]+sqrt(20000.0) }
详细看代码
#include <cstdio> #include <cstring> #include <cmath> #define inf 100000000.000 #define MAX 1010 double dp[2][MAX]; bool node[MAX][MAX]; int N,M,K; const double D=sqrt(1.*100*100+1.*100*100); void DP() { for(int j=0; j<=M; j++) dp[0][j]=dp[1][j]=inf; dp[1][0]=0; int c=0; while(c<=N) { for(int j=0; j<=M; j++) { if(j-1>=0 && dp[1][j-1]+100 < dp[1][j]) dp[1][j]=dp[1][j-1]+100; if(dp[0][j]+100 < dp[1][j]) dp[1][j]=dp[0][j]+100; if(node[c][j] && j-1>=0 && dp[0][j-1]+D < dp[1][j]) dp[1][j]=dp[0][j-1]+D; } for(int j=0; j<=M; j++) { dp[0][j]=dp[1][j]; dp[1][j]=inf; } c++; } printf("%.0f\n",dp[0][M]); return ; } int main() { while(scanf("%d%d",&N,&M)!=EOF) { scanf("%d",&K); int x,y; memset(node,0,sizeof(node)); for(int i=0; i<K; i++) { scanf("%d%d",&x,&y); node[x][y]=1; } DP(); } return 0; }