原题:http://acm.hdu.edu.cn/showproblem.php?pid=4881
题意:给一个20*20的带权网格图,每个结点处有红绿灯,颜色为红 - 绿 - 红, 绿灯时可以随便走,红灯只能右转,或者闯一次红灯扣光12分,给出绿灯亮起的时间,求S到T的最短路(到达时间 - 出发时间最短)
解题思路:
首先如果这个题如果要求的是到达时间最小,就简单了,直接spfa搞定,虽然写起来还是有点恶心
dis[x][y][dir][msk]表示现在在(x, y), 行驶方向是dir, msk表示是否已经闯过红灯,直接转移就可以
每次判断当前是否为绿灯,不是的话能不能等到绿灯亮起(因为绿灯灭了一次就不会再亮了, 所以有等到天荒地老都没用的情况)
如果需要闯红灯的话,更新一下msk就好,右转只要把方向向量按顺序写好,就可以直接用 (pre + 1) % 4 == cur来判断了
那么我们主要需要处理就是,如何解决出发时间的问题
首先我们如果从0时刻出发,到达T的时刻肯定是最早的,想要缩短到达 - 出发的时间,就要延后出发时间,或者是出发到达都延后,但出发延后的更多
那么我们延后出发时间可以缩短路上耗时的原因就是,路上有等灯的情况(我总是会不自觉的脑补inter -_-!)
与其在路上等,不如晚点走,但是晚多少出发?枚举肯定是不行的
我们考虑在某个结点X出等灯,那么绿灯亮起后肯定是立刻出发的,也就是说我们卡着绿的亮起的瞬间从结点X继续走
另外一种情况就是我路过X的时候,绿灯还会亮很久,时间有些浪费,所以可以晚一些出发,在X处卡着绿灯结束的时间过去就可以了(来的早不如来的巧)
但是,问题是最优解一定会卡在一个点吗?
答案是一定的,我们刨除闯红灯和红灯右转的部分,走绿灯的部分都有一个绿灯区间,我们把整体的时刻平移,早晚会有一个点卡在绿灯边界
因为出发时间有0做下限,所以可能无法向左平移,但是向右是一定可以的,所以必然存在最优解,舍得他到达某一个点是卡在绿灯熄灭的时刻
如果全程都没走绿灯,那么情况相当于任意时刻出发都一样,我们在枚举卡位的时候,可以把终点的绿灯时间设一个小于inf又足够大的值,让他卡在终点的绿灯熄灭时间到达
于是我们可以枚举所有可以卡位的点和卡位时间,即有绿等会亮起的结点,设为M
那么我们的路径分为两条,一条是S - M,另一条是M - T
M - T这一段和本文最开始说的要求到达时间最短是一个问题,因为已经出发了
那么S - M这一段可以倒过来求M - S的最短路,也就是获得从S最晚什么时候出发
两个方向都需要记录闯红灯和不闯红灯的结果,然后再合并
但是我们有一个问题,如果倒着回去S的时候,遇到了红灯怎么处理,这个时间关系可能比较复杂
我的做法是回去的路段遇到红灯必闯
假设从M - S的路径上有一个点遇到了红灯,不闯红灯就说明要在这里等,而等就说明了在这里出发是卡着绿灯亮起的时间的
那么这种情况完全可以再枚举卡在这个点绿灯亮起时间时求得,所以:
必然存在一种卡位使得从M - S 一路畅通无阻(可以用掉那次闯红灯的机会)
于是对于M - S的路径搜不通的情况下,就没有必要再去搜M - T,同时由于大部分情况都是没法搜通M - S的,所以我的代码才跑了62MS
代码:
<span style="font-family:Courier New;">// #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; #define M 21 #define inf 0x3f3f3f3f #define REP(i,a,b) for(int i = a; i <= b; ++i) struct Node { int x, y, dir, msk; Node(){} Node(int x, int y, int dir, int msk):x(x), y(y), dir(dir), msk(msk) {} }; int R[M][M], G[M][M], L[M][M][M][M], n, m; int dis[M][M][4][2], in[M][M][4][2]; int dx[] = {0, 1, 0, -1}; int dy[] = {1, 0, -1, 0}; int sx, sy, tx, ty; bool left(int pre, int cur) { return ((cur + 1) & 3) == pre; } bool right(int pre, int cur) { return ((pre + 1) & 3) == cur; } bool back(int mx, int my, int t, int a[]) { queue<Node> Q; memset(dis, -1, sizeof(dis)); REP(i,0,3) Q.push(Node(mx, my, i, 0)), in[mx][my][i][0] = true, dis[mx][my][i][0] = t; while( !Q.empty() ) { Node cur = Q.front(); Q.pop(); in[cur.x][cur.y][cur.dir][cur.msk] = false; REP(k, 0, 3) { int nx = cur.x + dx[k]; int ny = cur.y + dy[k]; if(nx < 1 || nx > n || ny < 1 || ny > m) continue; int ct = dis[cur.x][cur.y][cur.dir][cur.msk], nt = ct - L[cur.x][cur.y][nx][ny]; int nmsk = cur.msk; if((ct <= R[cur.x][cur.y] || ct > G[cur.x][cur.y]) && !left(cur.dir, k)) ++nmsk; if(nmsk > 1 || dis[nx][ny][k][nmsk] >= nt) continue; dis[nx][ny][k][nmsk] = nt; if(!in[nx][ny][k][nmsk]) Q.push(Node(nx, ny, k, nmsk)), in[nx][ny][k][nmsk] = true; } } a[0] = a[1] = -1; REP(i,0,3) REP(j,0,1) a[j] = max(a[j], dis[sx][sy][i][j]); if(max(a[0], a[1]) < 0) return false; return true; } void go(int mx, int my, int t, int b[]) { queue<Node> Q; memset(dis, 0x3f, sizeof(dis)); REP(i,0,3) Q.push(Node(mx, my, i, 0)), in[mx][my][i][0] = true, dis[mx][my][i][0] = t; while( !Q.empty() ) { Node cur = Q.front(); Q.pop(); in[cur.x][cur.y][cur.dir][cur.msk] = false; REP(k, 0, 3) { int nx = cur.x + dx[k]; int ny = cur.y + dy[k]; if(nx < 1 || nx > n || ny < 1 || ny > m) continue; int ct = dis[cur.x][cur.y][cur.dir][cur.msk], dlt = L[cur.x][cur.y][nx][ny], nt; int nmsk = cur.msk; if(R[cur.x][cur.y] + 1 <= G[cur.x][cur.y] && ct <= G[cur.x][cur.y]) { nt = max(R[cur.x][cur.y] + 1, ct) + dlt; if(dis[nx][ny][k][nmsk] > nt) { dis[nx][ny][k][nmsk] = nt; if(!in[nx][ny][k][nmsk]) Q.push(Node(nx, ny, k, nmsk)), in[nx][ny][k][nmsk] = true; } } if(!right(cur.dir, k)) ++nmsk; nt = ct + dlt; if(nmsk > 1 || dis[nx][ny][k][nmsk] <= nt) continue; dis[nx][ny][k][nmsk] = nt; if(!in[nx][ny][k][nmsk]) Q.push(Node(nx, ny, k, nmsk)), in[nx][ny][k][nmsk] = true; } } b[0] = b[1] = inf; REP(i,0,3) REP(j,0,1) b[j] = min(b[j], dis[tx][ty][i][j]); } int cal(int mx, int my, int t) { int a[2], b[2]; if(!back(mx, my, t, a)) return inf; go(mx, my, t, b); int ret = inf; REP(i,0,1) REP(j,0,1) if(i + j < 2) { if(a[i] != -1 && b[j] != inf) ret = min(ret, b[j] - a[i]); } return ret; } int solve() { int ret = inf; REP(i,1,n) REP(j,1,m) if(R[i][j] + 1 <= G[i][j]) { ret = min(ret, cal(i, j, G[i][j])); } return ret == inf ? -1 : ret; } int main() { int x, cas = 0; while( scanf( "%d%d", &n, &m ) != EOF ) { REP(i,1,n) REP(j,1,m) scanf( "%d", &R[i][j] ); REP(i,1,n) REP(j,1,m) scanf( "%d", &G[i][j] ); REP(i,1,n) REP(j,1,m - 1) { scanf( "%d", &x ); L[i][j][i][j+1] = L[i][j+1][i][j] = x; } REP(i,1,n - 1) REP(j,1,m) { scanf( "%d", &x ); L[i][j][i+1][j] = L[i+1][j][i][j] = x; } scanf( "%d%d%d%d", &sx, &sy, &tx, &ty ); R[tx][ty] = R[1][1] = R[1][m] = R[n][1] = R[n][m] = 0; G[tx][ty] = G[1][1] = G[1][m] = G[n][1] = G[n][m] = inf - 100; printf( "Case #%d: %d\n", ++cas, solve() ); } return 0; }</span>