题目来自 USACO
题目翻译见 NOCOW
思路
给地图,含两个出口,标准的最短路径问题。之前我在 codingames 某周的问题里也见过几乎同样的题,不过当时还不会写。
AC代码: 用 dijkstra
我先用 dijkstra 算法算了一下。里面遇到几个坑,调了好久才跑对。
- 输入的时候不能像上一个问题一样做一层循环,然后每行读 %s 进数组了,因为这个行里面有大量空格,%s 会自动结束的。因此我只会两重循环读 %c了。这里又两个坑,首先前面两个数字之后有个 \n 要读掉,还有每行结束的 \n 都要手动读掉。
- 寻找两个出口,我对最东、西、南、北的都检查了一遍。写到后来我又把读到的出口顺带换个字符堵上了,这样出口这个点可以和其它点一样,检查是否有四面墙即可,不用任何特殊处理。
我也不知道代码为什么会这么这么又丑又长,可能是这个地图被迫满篇写两重循环吧…待会我用可爱的 spfa 再做一下,也顺带看看题解有没有什么好写法不这么难看。
/*
ID:
LANG: C++
TASK: maze1
*/
#include
#include
#define hasNorthWall(x, y) (map[2 * (x) - 2][2 * (y) - 1] == '-')
#define hasEastWall(x, y) (map[2 * (x) - 1][2 * (y)] == '|')
#define hasSouthWall(x, y) (map[2 * (x)][2 * (y) - 1] == '-')
#define hasWestWall(x, y) (map[2 * (x) - 1][2 * (y) - 2] == '|')
//#define valid(x, y) (x >= 1 && x <= H && y >= 1 && y <= W)
#define min(x, y) ((x) < (y)? (x): (y))
const int maxW = 38, maxH = 100;
int W, H;
char map[2 * maxH + 1][2 * maxW + 1], temp; //地图
int exitX[2], exitY[2]; //两个出口
int minDist[2][maxH + 1][maxW + 1]; //对两个出口的最短距离
void dijkstra(int exit){
bool visited[maxH + 1][maxW + 1] = {0}; //最短距离是否确定
minDist[exit][exitX[exit]][exitY[exit]] = 1; //设出口的距离为1(往外走一步)
int i, j, x, y, d;
while(1){
//找现在unvisited的距离最短的
d = 40000;
for(i = 1; i <= H; i ++){
for(j = 1; j <= W; j ++){
if(!visited[i][j] && minDist[exit][i][j] < d){
d = minDist[exit][i][j];
x = i, y = j;
}
}
}
//都已经visit了,退出
if(d == 40000) break;
//加入visited
visited[x][y] = true;
//更新邻居
if(!hasNorthWall(x, y)) minDist[exit][x - 1][y] = min(minDist[exit][x - 1][y], d + 1);
if(!hasEastWall(x, y)) minDist[exit][x][y + 1] = min(minDist[exit][x][y + 1], d + 1);
if(!hasSouthWall(x, y)) minDist[exit][x + 1][y] = min(minDist[exit][x + 1][y], d + 1);
if(!hasWestWall(x, y)) minDist[exit][x][y - 1] = min(minDist[exit][x][y - 1], d + 1);
}
}
int main(){
FILE *fin = fopen("maze1.in", "r");
FILE *fout = fopen("maze1.out", "w");
fscanf(fin, "%d %d\n", &W, &H); //下面用%c读取,这里必须\n也过掉
for(int i = 0; i < 2 * H + 1; i ++){
for(int j = 0; j < 2 * W + 1; j ++){
fscanf(fin, "%c", &(map[i][j]));
}
fscanf(fin, "%c", &temp);//行末\n要过掉
}
//初始化最短路径为极大
for(int i = 1; i <= H; i ++){
for(int j = 1; j <= W; j ++){
minDist[0][i][j] = 40000;
minDist[1][i][j] = 40000;
}
}
//寻找两个出口
int nExit = 0;
for(int i = 1; i <= W; i ++){
if(!hasNorthWall(1, i)) {
exitX[nExit] = 1, exitY[nExit ++] = i;
map[2 * (1) - 2][2 * (i) - 1] = '-'; //堵上,方便后面统一计算
}
if(!hasSouthWall(H, i)) {
exitX[nExit] = H, exitY[nExit ++] = i;
map[2 * (H)][2 * (i) - 1] = '-';
}
}
for(int i = 1; i <= H; i ++){
if(!hasWestWall(i, 1)){
exitX[nExit] = i, exitY[nExit ++] = 1;
map[2 * (i) - 1][2 * (1) - 2] = '|';
}
if(!hasEastWall(i, W)){
exitX[nExit] = i, exitY[nExit ++] = W;
map[2 * (i) - 1][2 * (W)] = '|';
}
}
//找最短路径
dijkstra(0);
dijkstra(1);
//输出
int maxMin = 0, mi;
for(int i = 1; i <= H; i ++){
for(int j = 1; j <= W; j ++){
mi = min(minDist[0][i][j], minDist[1][i][j]);
if(mi > maxMin) maxMin = mi;
}
}
fprintf(fout, "%d\n", maxMin);
return 0;
}
因为数据量比较小,最大也就是 38 * 100 个方格,最多每个点 4 条边。while 循环最多执行 38000 次;里面找距离最短的还要 38000 次,但我记得教程讲这个事少,又有缓存的加成,可以认为比较快,也就是38000 ^ 2 次。
本来以为如果超时了就去搞个堆,或者改 spfa。结果这个 0.084 secs 的速度,还是吓到我了。看 nocow 上说 dijkstra 不算快的样子。我好像觉得,同样的算法,我尽管没有某些加速没有剪枝,写出来似乎总是比别人要快啊……上天眷顾我么??
Compiling...
Compile: OK
Executing...
Test 1: TEST OK [0.000 secs, 4220 KB]
Test 2: TEST OK [0.000 secs, 4220 KB]
Test 3: TEST OK [0.000 secs, 4220 KB]
Test 4: TEST OK [0.000 secs, 4220 KB]
Test 5: TEST OK [0.000 secs, 4220 KB]
Test 6: TEST OK [0.084 secs, 4220 KB]
Test 7: TEST OK [0.028 secs, 4220 KB]
Test 8: TEST OK [0.084 secs, 4220 KB]
Test 9: TEST OK [0.084 secs, 4220 KB]
Test 10: TEST OK [0.084 secs, 4220 KB]
All tests OK.
YOUR PROGRAM ('maze1') WORKED FIRST TIME! That's fantastic
-- and a rare thing. Please accept these special automated
congratulations.
使用 spfa
使用 spfa 算法,队列给了最大长度 3800。同时,改掉判断墙的四个宏定义,写成一个函数,把方向作为一个参数传进去,往每个方向引起的 x、y 的变化也存在数组里了,重复少了一些。
/*
ID: ufoshen1
LANG: C++
TASK: maze1
*/
#include
#include
#define min(x, y) ((x) < (y)? (x): (y))
const int maxW = 38, maxH = 100;
int W, H;
char map[2 * maxH + 1][2 * maxW + 1], temp; //地图
int exitX[2], exitY[2]; //两个出口
int minDist[2][maxH + 1][maxW + 1]; //对两个出口的最短距离
int deltaX[4] = {-1, 0, 1, 0}, deltaY[4] = {0, 1, 0, -1};
bool hasWall(int x, int y, int dir){
switch(dir){ //0N 1E 2S 3W
case 0: return (map[2 * (x) - 2][2 * (y) - 1] == '-');
case 1: return (map[2 * (x) - 1][2 * (y)] == '|');
case 2: return (map[2 * (x)][2 * (y) - 1] == '-');
case 3: return (map[2 * (x) - 1][2 * (y) - 2] == '|');
}
}
void spfa(int exit){
bool inq[maxH + 1][maxW + 1];//是否在队列中
minDist[exit][exitX[exit]][exitY[exit]] = 1; //出口距离为1
int queue[38000][2], start = 0, end = 1; //队列
queue[0][0] = exitX[exit], queue[0][1] = exitY[exit]; //出口入队
int x, y, nbrX, nbrY, newD;
while(end > start){
//出队
x = queue[start][0], y = queue[start ++][1];
inq[x][y] = false;
//四个邻居
for(int dir = 0; dir < 4; dir ++){
if(hasWall(x, y, dir)) continue;
//没墙,连通
nbrX = x + deltaX[dir], nbrY = y + deltaY[dir];
newD = minDist[exit][x][y] + 1;
if(minDist[exit][nbrX][nbrY] > newD){
minDist[exit][nbrX][nbrY] = newD;
if(!inq[nbrX][nbrY]){
queue[end][0] = nbrX, queue[end ++][1] = nbrY;
inq[nbrX][nbrY] = true;
}
}
}
}
}
int main(){
FILE *fin = fopen("maze1.in", "r");
FILE *fout = fopen("maze1.out", "w");
fscanf(fin, "%d %d\n", &W, &H); //下面用%c读取,这里必须\n也过掉
for(int i = 0; i < 2 * H + 1; i ++){
for(int j = 0; j < 2 * W + 1; j ++){
fscanf(fin, "%c", &(map[i][j]));
}
fscanf(fin, "%c", &temp);//行末\n要过掉
}
//初始化最短路径为极大
for(int i = 1; i <= H; i ++){
for(int j = 1; j <= W; j ++){
minDist[0][i][j] = 40000;
minDist[1][i][j] = 40000;
}
}
//寻找两个出口
int nExit = 0;
for(int i = 1; i <= W; i ++){
if(!hasWall(1, i, 0)) {
exitX[nExit] = 1, exitY[nExit ++] = i;
map[2 * (1) - 2][2 * (i) - 1] = '-'; //堵上,方便后面统一计算
}
if(!hasWall(H, i, 2)) {
exitX[nExit] = H, exitY[nExit ++] = i;
map[2 * (H)][2 * (i) - 1] = '-';
}
}
for(int i = 1; i <= H; i ++){
if(!hasWall(i, 1, 3)){
exitX[nExit] = i, exitY[nExit ++] = 1;
map[2 * (i) - 1][2 * (1) - 2] = '|';
}
if(!hasWall(i, W, 1)){
exitX[nExit] = i, exitY[nExit ++] = W;
map[2 * (i) - 1][2 * (W)] = '|';
}
}
//找最短路径
spfa(0);
spfa(1);
//输出
int maxMin = 0, mi;
for(int i = 1; i <= H; i ++){
for(int j = 1; j <= W; j ++){
mi = min(minDist[0][i][j], minDist[1][i][j]);
if(mi > maxMin) maxMin = mi;
}
}
fprintf(fout, "%d\n", maxMin);
return 0;
}
运行速度吓掉了下巴,全部 0.000 秒AC。这两个算法感觉都挺好写的,可能因为刚写过没多久吧……
Compiling...
Compile: OK
Executing...
Test 1: TEST OK [0.000 secs, 4396 KB]
Test 2: TEST OK [0.000 secs, 4400 KB]
Test 3: TEST OK [0.000 secs, 4400 KB]
Test 4: TEST OK [0.000 secs, 4404 KB]
Test 5: TEST OK [0.000 secs, 4404 KB]
Test 6: TEST OK [0.000 secs, 4400 KB]
Test 7: TEST OK [0.000 secs, 4404 KB]
Test 8: TEST OK [0.000 secs, 4400 KB]
Test 9: TEST OK [0.000 secs, 4400 KB]
Test 10: TEST OK [0.000 secs, 4396 KB]
All tests OK.
Your program ('maze1') produced all correct answers! This is your
submission #2 for this problem. Congratulations!