Uvalive 4128 - Steam Roller(最短路+思维)
题目链接 https://vjudge.net/problem/UVALive-4128
【题意】
有一个r条横线c条竖线组成的网络,你的任务是开着一辆蒸汽式压路机,用最短的时间从(r1,c1)出发,最后到达目的地(r2,c2),其中一些线段上有权值代表全速通过所需要的时间,权值为0的边表示不能通过。由于压路机的惯性较大,对于任意一条边,如果进入这条边之前刚转弯或者离开这条边以后要立即转弯,则实际时间是理想时间的2倍,并且整条路线的起点和终点所在的边也需要两倍时间,“时间加倍”不可叠加。对于每组数据,求出最短时间,无法到达输出”Impossible”
【思路】
大白书336页原题,把某个位置(r,c)拆分成8种不同状态(r,c,dir,doubled)代表上一步从哪个方向(dir)移动到这个点,以及上条边是否已经加倍。然后计算出每种状态的后继状态,建立有向边,最后套用dijkstra算法模板求解,思路不算复杂,但是细节实在特别多,基本上是把书上的代码看会又抄一遍。
#include<bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 80500;
struct Edge {
int from, to, dist;
Edge(int f, int t, int d) :from(f), to(t), dist(d) {}
};
struct HeapNode {
int d, u;
HeapNode(int dd, int uu) :d(dd), u(uu) {}
bool operator<(const HeapNode& rhs)const {
return d > rhs.d;
}
};
struct Dijkstra {
int n, m;
vector<Edge> edges;
vector<int> g[maxn];
bool done[maxn];
int d[maxn];
int p[maxn];
void init(int n) {
this->n = n;
for (int i = 0; i < n; ++i)g[i].clear();
edges.clear();
}
void add(int from, int to, int dist) {
edges.push_back(Edge(from, to, dist));
m = edges.size();
g[from].push_back(m - 1);
}
void dijkstra(int s) {
priority_queue<HeapNode> que;
for (int i = 0; i < n; ++i) d[i] = inf;
d[s] = 0;
memset(done, 0, sizeof(done));
que.push(HeapNode(0, s));
while (!que.empty()) {
HeapNode x = que.top();
que.pop();
int u = x.u;
if (done[u])continue;
done[u] = true;
for (int i = 0; i < g[u].size(); ++i) {
Edge& e = edges[g[u][i]];
if (d[e.to] > d[u] + e.dist) {
d[e.to] = d[u] + e.dist;
p[e.to] = g[u][i];
que.push(HeapNode(d[e.to], e.to));
}
}
}
}
};
const int UP = 0, LEFT = 1, DOWN = 2, RIGHT = 3;
const int dr[4] = { -1,0,1,0 };
const int dc[4] = { 0,-1,0,1 };
const int inv[4] = { 2,3,0,1 };//记录反方向的下标
const int maxr = 105;
const int maxc = 105;
int readint() { int x; scanf("%d", &x); return x; }
int n;//结点总数
int grid[maxr][maxc][4];//grid[r][c][dir]记录从(r,c)沿dir方向出发的边权
int id[maxr][maxc][4][2];//id[r][c][dir][double]记录状态为(r,c,dir,double)的结点编号
int R, C, r1, c1, r2, c2;
Dijkstra solver;
int ID(int r, int c, int dir, int doubled) {//给结点编号
int& x = id[r][c][dir][doubled];
if (x == 0) x = ++n;//编号从1开始
return x;
}
bool cango(int r, int c, int dir) {//从(r,c)处是否可以沿着dir方向走
if (r < 0 || r >= R || c < 0 || c >= C) return false;
return grid[r][c][dir] > 0;
}
int main() {
int kase = 0;
while (scanf("%d%d%d%d%d%d", &R, &C, &r1, &c1, &r2, &c2) == 6 && R) {//输入和初始化
--r1, --c1, --r2, --c2;
memset(grid, 0, sizeof(grid));
for (int i = 0; i < R; ++i) {
for (int j = 0; j < C - 1; ++j) {
grid[i][j][RIGHT] = grid[i][j + 1][LEFT] = readint();
}
if (i + 1 != R) {
for (int j = 0; j < C; ++j) {
grid[i][j][DOWN] = grid[i + 1][j][UP] = readint();
}
}
}
solver.init(8 * R*C + 1);//源点编号为0
n = 0;
memset(id, 0, sizeof(id));
//源点出发的边
for (int dir = 0; dir < 4; ++dir) {
if (cango(r1, c1, dir)) {
solver.add(0, ID(r1 + dr[dir], c1 + dc[dir], dir, 1), grid[r1][c1][dir] * 2);
}
}
//计算每个状态的后继状态
for (int i = 0; i < R; ++i)
for (int j = 0; j < C; ++j)
for (int dir = 0; dir < 4; ++dir)
if (cango(i, j, inv[dir]))
for (int newdir = 0; newdir < 4; ++newdir)
if (cango(i, j, newdir))
for (int doubled = 0; doubled < 2; ++doubled) {
int newr = i + dr[newdir];
int newc = j + dc[newdir];
int v = grid[i][j][newdir], newdoubled = 0;
if (dir != newdir) {
if (0 == doubled) { v += grid[i][j][inv[dir]]; }
newdoubled = 1;
v += grid[i][j][newdir];
}
solver.add(ID(i, j, dir, doubled), ID(newr, newc, newdir, newdoubled), v);
}
solver.dijkstra(0);
int ans = inf;
for (int dir = 0; dir < 4; ++dir) {
for (int doubled = 0; doubled < 2; ++doubled) {
int v = solver.d[ID(r2, c2, dir, doubled)];
if (0 == doubled) v += grid[r2][c2][inv[dir]];
ans = min(ans, v);
}
}
printf("Case %d: ", ++kase);
if (ans == inf) printf("Impossible\n");
else printf("%d\n", ans);
}
return 0;
}