给定起点和终点,求网格图中的最短路,其中每条边如果在头部或尾部改变方向,那么其长度翻倍。
首先想状态,因为有翻倍这个规则,我们需要知道来到某个点时面对的方向。
但有可能在来到某个点之前不知道是否需要翻倍,但有的又已经确定并翻倍,那么有必要另用一个 0/1 状态记录。
设 \(f(i, j, dir, doubled)\) 表示:到点 \((i,j)\),来的方向为 \(dir\),来的边是否已经翻倍,到达这个状态的最短距离。
之后的难点是建图,实际上直接枚举每个状态可以到达的其它状态即可。
之后可以设一个超级源点 \(s\),将其与所有起点可以一步到达的状态连边即可。
然后是最短路的板子。
由于状态总量 \(N=r\times c\times 4\times 2\),总边数最大为 \(M=N\times 4\)。
利用 Dijkstra 的时间复杂度为 \(O(M\log N)\),完全可以通过本题。
码量较大,细节上需多加注意。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 110;
const int M = N * N * 10;
const int INF = 0x3f3f3f3f;
const int UP = 0, LEFT = 1, RIGHT = 2, DOWN = 3;
const int inv[] = {DOWN, RIGHT, LEFT, UP};
const int dx[] = {-1, 0, 0, 1};
const int dy[] = {0, -1, 1, 0};
int n, m, r1, c1, r2, c2, tot, G[N][N][4], Id[N][N][4][2];
int cnt, head[M], dis[M], vis[M];
struct Edge{int nxt, to, val;} ed[M * 10];
priority_queue<pair<int, int> >q;
void clear(){
tot = cnt = 0;
memset(G, 0, sizeof(G));
memset(Id, 0, sizeof(Id));
memset(head, 0, sizeof(head));
}
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
int ID(int x, int y, int dir, int doubled){
int& now = Id[x][y][dir][doubled];
return now ? now : (now = ++ tot);
}
bool In(int x, int y, int dir){
if(x < 1 || x > n || y < 1 || y > m) return false;
return G[x][y][dir] > 0;
}
void add(int u, int v, int w){
ed[++ cnt] = (Edge){head[u], v, w};
head[u] = cnt;
}
void Dijkstra(){
int s = 0;
for(int dir = 0; dir < 4; dir ++) if(In(r1, c1, dir))
add(s, ID(r1 + dx[dir], c1 + dy[dir], dir, 1), G[r1][c1][dir] * 2);
memset(dis, 0x3f, sizeof(dis));
memset(vis, false, sizeof(vis));
q.push(make_pair(0, s)), dis[s] = 0;
while(!q.empty()){
int u = q.top().second; q.pop();
if(vis[u]) continue;
vis[u] = true;
for(int i = head[u]; i; i = ed[i].nxt){
int v = ed[i].to, w = ed[i].val;
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
q.push(make_pair(-dis[v], v));
}
}
}
}
int main(){
int Case = 0;
while(~scanf("%d %d %d %d %d %d", &n, &m, &r1, &c1, &r2, &c2) && n){
clear();
for(int i = 1; i <= n; i ++){
for(int j = 1; j < m; j ++)
G[i][j][RIGHT] = G[i][j + 1][LEFT] = read();
if(i != n) for(int j = 1; j <= m; j ++)
G[i][j][DOWN] = G[i + 1][j][UP] = read();
}
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
for(int dir = 0; dir < 4; dir ++) if(In(i, j, inv[dir]))
for(int newdir = 0; newdir < 4; newdir ++) if(In(i, j, newdir))
for(int doubled = 0; doubled < 2; doubled ++){
int gi = i + dx[newdir];
int gj = j + dy[newdir];
int len = G[i][j][newdir], newdoubled = 0;
if(dir != newdir){
if(!doubled) len += G[i][j][inv[dir]];
newdoubled = 1, len += G[i][j][newdir];
}
add(ID(i, j, dir, doubled), ID(gi, gj, newdir, newdoubled), len);
}
Dijkstra();
int ans = INF;
for(int dir = 0; dir < 4; dir ++) if(In(r2, c2, inv[dir]))
for(int doubled = 0; doubled < 2; doubled ++){
int v = dis[ID(r2, c2, dir, doubled)];
if(!doubled) v += G[r2][c2][inv[dir]];
ans = min(ans, v);
}
printf("Case %d: ", ++ Case);
if(ans == INF) puts("Impossible"); else printf("%d\n", ans);
}
return 0;
}