题目:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2129
题目大意:给你一张格子图,r 根横线, c 根竖线。告诉你起点和终点,然后从起点走,每条边有权值,如果是0,就表示无法通行。走的规则是(通俗点):车子的惯性比较大,如果你在下个路口要转弯,那么后半段就开慢了,好转弯,转弯完了之后,你要加速,那么前半段就慢了,这两种情况都会使这段路的时间加倍,但是如果一条路同时是这样,那么也只算两倍。起点这终点一个启动,一个是制动,那么和他们相连的第一条边也算两倍。问你最短时间,如果不行,就输出 “Impossible” 。
解题思路:最短路问题,关键是怎么设计状态。可是它的状态比较多,每个点是状态节点,那么它肯定有它的坐标 x、y,由于跟到达这个节点的方向有关,以判断是不是转弯,那么再来一维表示方向,还有就是如果同时前段路慢了和后段路慢了,那么也是两倍的问题,可以再加一维来表示到达这个节点的这段路有没有加倍过。这样状态就设计完成了,在写最短路就行了。但是注意:这里有坑,就是到达终点的时候,由于边权加倍规则的存在,那么到达终点了可以再往前走一段,再回来,也就是说,本来不是说到达终点的边也要加倍,但是并不是在更新的时候直接判断加倍,这样会有问题,我的处理方式是,终点和其他点的处理方式一样,从队列里拿粗来的时候,判断是不是终点,然后再看 doubled ,就是到达终点的这条边正常模式下有没有加倍,如果没有就加倍,然后更新 ans。
看了点书上的思路,然后自己写的代码,发现,本人和 LRJ 的写法有很大的不同,他是给状态编号,然后建边,再原封不动地套模板,就是之前做好所有准备,然后直接套。而我是直接改模板写。有时间,我在敲敲 LRJ 的写法,看看哪种比较好。
这道题目 RE 了一发,因为双向边的问题,然后又 WA 了一发,原因竟然是我最后在输出的时候,case 那里我换行了,然后在输出答案,害我检查了好久上面的代码。。。= =
代码如下:
#include
#include
#include
#include
using namespace std;
const int INF = 0x0fffffff;
const int MAXN = 111;
struct Edge
{
int t_x,t_y,val,next;
int dir;
} edge[MAXN*MAXN*2*2];
int tot,head[MAXN][MAXN];
void init()
{
memset(head,-1,sizeof(head));
tot = 0;
}
void add_edge(int s_x,int s_y,int t_x,int t_y,int val,int dir)
{
edge[tot].dir = dir;
edge[tot].t_x = t_x;
edge[tot].t_y = t_y;
edge[tot].val = val;
edge[tot].next = head[s_x][s_y];
head[s_x][s_y] = tot++;
}
struct Node
{
int x,y;
int val;
int last_dir,last_edge_val,doubled;
Node(int a,int b,int c,int d,int e,int f)
{
x = a;y = b;val = c;last_dir = d;last_edge_val = e;doubled = f;
}
bool operator < (const Node& tmp) const
{
return val > tmp.val;
}
};
int done[MAXN][MAXN][11][11];
int d[MAXN][MAXN][11][11];
int ans;
void dij(int r,int c,int r1,int c1,int r2,int c2)
{
priority_queue q;
memset(done,0,sizeof(done));
for(int i = 0;i < r;i++)
for(int j = 0;j < c;j++)
for(int dir = 0;dir < 4;dir++)
for(int k = 0;k < 2;k++)
d[i][j][dir][k] = INF;
d[r1][c1][4][1] = 0;
q.push(Node(r1,c1,0,4,0,1));
while(!q.empty())
{
Node cur = q.top();
q.pop();
if(cur.x == r2 && cur.y == c2)
{
int tmp;
if(cur.doubled)
tmp = cur.val;
else tmp = cur.val+cur.last_edge_val;
ans = min(ans,tmp);
}
if(done[cur.x][cur.y][cur.last_dir][cur.doubled]) continue;
done[cur.x][cur.y][cur.last_dir][cur.doubled] = 1;
for(int e = head[cur.x][cur.y];e != -1;e = edge[e].next)
{
int xx = edge[e].t_x;
int yy = edge[e].t_y;
int val = edge[e].val;
int dir = edge[e].dir;
int old_dif = 0;
if(!cur.doubled)
{
if(dir != cur.last_dir)
old_dif = 1;
}
int new_dif = 1;
int next_doubled = 0;
if(cur.last_dir != dir)
{
new_dif = 2;
next_doubled = 1;
}
int tmp = d[cur.x][cur.y][cur.last_dir][cur.doubled]+new_dif*val+old_dif*cur.last_edge_val;
if(d[xx][yy][dir][next_doubled] > tmp)
{
//printf("%d,%d,%d,%d,tmp = %d\n",cur.x,cur.y,xx,yy,tmp);
d[xx][yy][dir][next_doubled] = tmp;
q.push(Node(xx,yy,tmp,dir,val,next_doubled));
}
}
}
}
int main()
{
int cas = 0;
int r,c,r1,c1,r2,c2;
while(~scanf("%d%d%d%d%d%d",&r,&c,&r1,&c1,&r2,&c2))
{
if(r+c == 0) break;
r1--;c1--;r2--;c2--;
init();
for(int i = 0;i < 2*r-1;i++)
{
int val;
if(i&1)
{
for(int j = 0;j < c;j++)
{
scanf("%d",&val);
if(val == 0) continue;
add_edge(i/2+1,j,i/2,j,val,3);
add_edge(i/2,j,i/2+1,j,val,1);
}
}
else
{
for(int j = 0;j < c-1;j++)
{
scanf("%d",&val);
if(val == 0) continue;
add_edge(i/2,j,i/2,j+1,val,0);
add_edge(i/2,j+1,i/2,j,val,2);
}
}
}
ans = INF;
dij(r,c,r1,c1,r2,c2);
printf("Case %d: ",++cas);
if(ans >= INF) puts("Impossible");
else printf("%d\n",ans);
}
return 0;
}
/*
2 2 1 1 2 2
10
9 10
9
4 4 1 1 4 4
10 10 10
9 0 0 10
0 0 0
9 0 0 10
9 0 0
0 9 0 10
0 9 9
*/
模仿 LRJ 书中的写法,注意 can_go 那里的条件限制,WA了好久,感觉还是比较适应自己的写法,代码如下:
#include
#include
#include
#include
using namespace std;
const int INF = 0x0fffffff;
const int MAXN = 111*111*11*11;
const int MAXM = 111*111*2*11*4*11;
int dir_x[] = {0,1,0,-1},dir_y[] = {1,0,-1,0};
int inv[] = {2,3,0,1};
struct Edge
{
int t,val,next;
}edge[MAXM];
struct Node
{
int id,val;
Node(int a,int b)
{
id = a;val = b;
}
bool operator < (const Node& tmp) const
{
return val > tmp.val;
}
};
int d[MAXN];
int n;
struct Dij
{
int tot,head[MAXN];
void init()
{
tot = 0;
memset(head,-1,sizeof(head));
}
void add_edge(int s,int t,int val)
{
edge[tot].t = t;
edge[tot].val = val;
edge[tot].next = head[s];
head[s] = tot++;
}
bool done[MAXN];
void dijkstra(int s)
{
priority_queue q;
memset(done,0,sizeof(done));
for(int i = 0;i <= n;i++) d[i] = INF;
d[s] = 0;
q.push(Node(s,0));
while(!q.empty())
{
Node cur = q.top();
q.pop();
if(done[cur.id]) continue;
done[cur.id] = 1;
for(int i = head[cur.id];i != -1;i = edge[i].next)
{
int t = edge[i].t;
int val = edge[i].val;
int tmp = d[cur.id]+val;
if(d[t] > tmp)
{
d[t] = tmp;
q.push(Node(t,tmp));
}
}
}
}
} dij;
int id[111][111][11][11];
int get_id(int i,int j,int dir,int doubled)
{
int& x = id[i][j][dir][doubled];
if(x == 0) x = ++n;
return x;
}
int grid[111][111][11];
int read_int()
{
int x;
scanf("%d",&x);
return x;
}
int can_go(int i,int j,int val,int r,int c)
{
if(i < 0 || i >= r || j < 0 || j >= c || val == 0) return 0;
return 1;
}
int main()
{
int cas = 0;
int r,c,r1,c1,r2,c2;
while(~scanf("%d%d%d%d%d%d",&r,&c,&r1,&c1,&r2,&c2))
{
if(r == 0 && c == 0) break;
r1--;c1--;r2--;c2--;
for(int i = 0;i < r;i++)
{
for(int j = 0;j < c-1;j++)
{
grid[i][j][0] = grid[i][j+1][2] = read_int();
}
if(i != r-1)
{
for(int j = 0;j < c;j++)
grid[i][j][1] = grid[i+1][j][3] = read_int();
}
}
dij.init();
n = 0;
memset(id,0,sizeof(id));
for(int dir = 0;dir < 4;dir++)
{
int ii = r1+dir_x[dir];
int jj = c1+dir_y[dir];
if(can_go(ii,jj,grid[r1][c1][dir],r,c))
dij.add_edge(0,get_id(ii,jj,dir,1),grid[r1][c1][dir]*2);
}
for(int i = 0;i < r;i++)
for(int j = 0;j < c;j++)
for(int last_dir = 0;last_dir < 4;last_dir++)
for(int doubled = 0;doubled < 2;doubled++)
{
for(int new_dir = 0;new_dir < 4;new_dir++)
{
int ii = i+dir_x[new_dir];
int jj = j+dir_y[new_dir];
if(!can_go(ii,jj,grid[i][j][inv[last_dir]],r,c)) continue;
if(!can_go(ii,jj,grid[i][j][new_dir],r,c)) continue;
int new_dif = 1;
int new_doubled = 0;
int old_dif = 0;
if(last_dir != new_dir)
{
new_dif = 2;
new_doubled = 1;
if(!doubled)
old_dif = 1;
}
int val = old_dif*grid[i][j][inv[last_dir]]+new_dif*grid[i][j][new_dir];
dij.add_edge(get_id(i,j,last_dir,doubled),get_id(ii,jj,new_dir,new_doubled),val);
}
}
dij.dijkstra(0);
int ans = INF;
for(int dir = 0;dir < 4;dir++) if(can_go(r2,c2,grid[r2][c2][inv[dir]],r,c))
for(int doubled = 0;doubled < 2;doubled++)
{
int id = get_id(r2,c2,dir,doubled);
if(doubled)
ans = min(ans,d[id]);
else
{
int tmp = d[id]+grid[r2][c2][inv[dir]];
ans = min(ans,tmp);
}
}
printf("Case %d: ",++cas);
if(ans >= INF) puts("Impossible");
else printf("%d\n",ans);
}
return 0;
}
/*
1 2 1 1 1 2
0
2 2 1 1 2 2
10
9 10
9
1 4 1 1 1 4
10 10 10
4 4 1 1 4 4
10 10 10
9 0 0 10
0 0 0
9 0 0 10
9 0 0
0 9 0 10
0 9 9
2 2 1 1 2 2 0 1 1 0
*/
书上的第二种解法:除了四个方向以外,再增加一个方向:静止,那么状态就是 d(i,j,0~4)。点分静点和动点。一个动点可以变成以之前方向相同的静点,边权*2,,也可以变成同个运动方向的动点,边权*1;静点能变成各个方向的动点,也能直接变成静点(不叠加),边权都*2。这样一来,思路就比较清楚了,这种增加一个静止方向的方法值得学习。
代码如下:
#include
#include
#include
#include
using namespace std;
const int INF = 0x0fffffff;
const int MAXN = 111;
const int MAXM = 111*111*2*2;
struct Edge
{
int t_x,t_y,next,val,dir;
}edge[MAXM];
int tot,head[MAXN][MAXN];
void add_edge(int s_x,int s_y,int t_x,int t_y,int val,int dir)
{
edge[tot].dir = dir;
edge[tot].val = val;
edge[tot].t_x = t_x;
edge[tot].t_y = t_y;
edge[tot].next = head[s_x][s_y];
head[s_x][s_y] = tot++;
}
void init()
{
tot = 0;
memset(head,-1,sizeof(head));
}
struct Node
{
int x,y,dir,val;
Node(int a,int b,int c,int d)
{
x = a;y = b;
dir = c;val = d;
}
bool operator < (const Node& tmp) const
{
return val > tmp.val;
}
};
int done[MAXN][MAXN][11],d[MAXN][MAXN][11];
void dij(int r1,int c1,int r2,int c2,int r,int c)
{
priority_queue q;
memset(done,0,sizeof(done));
for(int i = 0;i < r;i++)
for(int j = 0;j < c;j++)
for(int dir = 0;dir < 5;dir++)
d[i][j][dir] = INF;
d[r1][c1][4] = 0;
q.push(Node(r1,c1,4,0));
while(!q.empty())
{
Node cur = q.top();
q.pop();
if(done[cur.x][cur.y][cur.dir]) continue;
done[cur.x][cur.y][cur.dir] = 1;
//printf("x = %d,y = %d,val = %d,dir = %d\n",cur.x,cur.y,cur.val,cur.dir);
if(cur.dir == 4)
{
for(int i = head[cur.x][cur.y];i != -1;i = edge[i].next)
{
int xx = edge[i].t_x;
int yy = edge[i].t_y;
int dir = edge[i].dir;
int tmp = cur.val+edge[i].val*2;
if(tmp < d[xx][yy][dir])
{
d[xx][yy][dir] = tmp;
q.push(Node(xx,yy,dir,tmp));
}
if(tmp < d[xx][yy][4])
{
d[xx][yy][4] = tmp;
q.push(Node(xx,yy,4,tmp));
}
}
}
else
{
for(int i = head[cur.x][cur.y];i != -1;i = edge[i].next)
{
if(edge[i].dir == cur.dir)
{
int xx = edge[i].t_x;
int yy = edge[i].t_y;
int tmp = cur.val+edge[i].val;
if(tmp < d[xx][yy][cur.dir])
{
d[xx][yy][cur.dir] = tmp;
q.push(Node(xx,yy,cur.dir,tmp));
}
tmp = cur.val+edge[i].val*2;
if(tmp < d[xx][yy][4])
{
d[xx][yy][4] = tmp;
q.push(Node(xx,yy,4,tmp));
}
}
}
}
}
}
int main()
{
int cas = 0;
int r,c,r1,c1,r2,c2;
while(~scanf("%d%d%d%d%d%d",&r,&c,&r1,&c1,&r2,&c2))
{
if(r+c == 0) break;
r1--;c1--;r2--;c2--;
init();
for(int i = 0;i < 2*r-1;i++)
{
int val;
if(i&1)
{
for(int j = 0;j < c;j++)
{
scanf("%d",&val);
if(val == 0) continue;
add_edge(i/2,j,i/2+1,j,val,1);
add_edge(i/2+1,j,i/2,j,val,3);
}
}
else
{
for(int j = 0;j < c-1;j++)
{
scanf("%d",&val);
if(val == 0) continue;
add_edge(i/2,j,i/2,j+1,val,0);
add_edge(i/2,j+1,i/2,j,val,2);
}
}
}
dij(r1,c1,r2,c2,r,c);
printf("Case %d: ",++cas);
int ans = d[r2][c2][4];
if(ans >= INF) puts("Impossible");
else printf("%d\n",ans);
}
return 0;
}
/*
2 2 1 1 2 2
10
9 10
9
1 4 1 1 1 4
10 10 10
4 4 1 1 4 4
10 10 10
9 0 0 10
0 0 0
9 0 0 10
9 0 0
0 9 0 10
0 9 9
*/