一、题目链接

​屠龙宝刀点击就送​

解法一:广度优先搜索

(一)思路

本题的路线可延伸到矩形板的外面,延伸多远都没关系。但是因为题目要求的是最小的线段数量,则伸出一个格子跟伸出多个格子没有区别。为了简单起见,伸出一个格子就好。这样就可以把原来的矩形板的面积增加一圈,即列从[1, w]扩大为[0, w+1],行从[1, h]扩大为[0, h+1]。

本题可使用广度优先搜索来解决。每个head格子出队后,周围最多有四个不同方向的相邻格子可入队。当某个方向的相邻格子入队后,优先往这个方向一直前进,在这个过程中不需要拐弯,经过的格子也不需要入队,因为入队会导致拐弯次数增加。

(二)代码

#include <iostream>
#include <cstdio>
#include <queue>
#include <memory.h>
using namespace std;

struct grid
{
int x; //本题中x为列
int y; //y为行
int step; //第几条线段,即变了几次方向
};

int dx[]={0, 0, 0, -1, 1};//移动列
int dy[]={0, -1, 1, 0, 0};//移动行
int sx, sy, ex, ey, w, h;

const int maxN = 80;
char Map[maxN][maxN];
bool vis[maxN][maxN];

bool check(int x, int y)
{
//因为可跑到矩形板的外面,所以四个边界都要加1
if(x >= 0 && x <= w+1 && y >= 0 && y <= h+1 && !vis[x][y])
{
if(Map[x][y] == ' ' || (Map[x][y] == 'X' && x == ex && y == ey))
{
return true;
}

return false;
}

return false;
}

queue<grid>q;

void bfs()
{
grid start;
start.x = sx;
start.y = sy;
start.step = 0;

vis[sx][sy] = true;
q.push(start);

while(!q.empty())
{
grid head = q.front();
q.pop();
grid Next; //相邻的四个格子
for(int i=1; i<=4; i++)
{
Next.x = head.x + dx[i];
Next.y = head.y + dy[i];
Next.step = head.step + 1; //每换个方向,线段数就会加1,所以step加1
while(check(Next.x, Next.y))
{
if(Next.x == ex && Next.y == ey)
{
printf("%d segments.\n", Next.step);
return;
}

vis[Next.x][Next.y] = true;
q.push(Next);

//往dx[i],dy[i]这个固定的方向一直前进,不会引起线段数量增加,所以step不加1
//处于固定方向一直前进中途上的各个点,不需要进队列,因为进队列就意味着可调方向,
//调方向得到的线段数必然多于不调方向得到的线段树。
Next.x += dx[i];
Next.y += dy[i];
}
}
}

printf("impossible.\n");

return;
}

int main()
{
//freopen("game.in", "r", stdin);

int cnt=0;

while(true)
{
cnt++;
scanf("%d%d", &w, &h); //宽(列)和高(行)
if(w==0 && h==0)
{
break;
}

memset(Map, ' ', sizeof(Map));

getchar(); //吸收换行符

for(int r = 1; r <= h; r++) //行
{
string str;
getline(cin, str);
for (int c = 1; c <= w; c++)//列
{
Map[c][r] = str[c - 1];
}
}

printf("Board #%d:\n", cnt);
int pairCnt = 0;

while(true)
{
pairCnt++;
scanf("%d%d%d%d", &sx, &sy, &ex, &ey);
if(sx==0 && sy==0 && ex==0 && ey==0)
{
break;
}

printf("Pair %d: ", pairCnt);
memset(vis, false, sizeof(vis));

bfs();

//注意queue没有clear()函数,要清空得逐个弹出
while(!q.empty())
{
q.pop();
}
}
printf("\n");
}

return 0;
}

解法二:深度优先搜索

(一)思路

本题也可以使用深搜来解决。对于每个格子,都有四种前进的方向。如果相邻两个格子的前进方向一致,则线段总数不变,如果方向不一样,则线段总数加1。

在使用深搜时,因为用到了递归,所以最先算出的是终点到终点的线段数量(不用挪动,线段数量为0),再往回计算终点旁边的点到终点的线段数量,再往回推算与终点距离为2的点到终点的线段数量,……,最后计算出起点到终点的线段数量。

OpenJudge 1804《小游戏》题解报告_线段数


如上图所示,假设D是终点,则D到D的线段数量为0。C到D的线段数量为1。A到C的方向与C到D的方向不一样,所以线段数量为2。B到C的方向与C到D的方向一样,则B到D的线段数量为1。

(二)代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int w, h;
int dir[8] = {0, -1, 1, 0, 0, 1, -1, 0}; //每两个数对应一个移动方向,顺序为上、右、下、左
const int SIZE = 80;
bool board[SIZE][SIZE]; //存储棋盘,有卡片为true,空为false
int seg[SIZE][SIZE][4]; //存储从点(curX,curY)开始,第一步方向是r,最少需要多少条线段到达目标点
const int INF = 99999999; //因为为+1操作,不能用INT_MAX
int startX, startY, endX, endY; //起点坐标和终点坐标

int dfs(int curX, int curY, int d) //d是direction的缩写,表示方向
{
//记忆化存储,已经计算过就不用再次计算了
if (seg[curX][curY][d])
{
return seg[curX][curY][d];
}

//终点到终点不用走,需要0条线段
if (curX == endX && curY == endY)
{
for (int i = 0; i < 4; i++)
{
seg[curX][curY][i] = 0;
}

return 0;
}

//带卡片的格子走不到终点,用INF来表示
if (board[curY][curX])
{
for (int i = 0; i < 4; i++) //四个方向
{
seg[curX][curY][i] = INF;
}

return INF;
}

seg[curX][curY][d] = INF;
int nextX = curX + dir[2 * d];
int nextY = curY + dir[2 * d + 1];
if (nextX >= 0 && nextX <= w + 1 && nextY >= 0 && nextY <= h + 1)
{
for (int i = 0; i < 4; i++)//四个方向
{
seg[nextX][nextY][i] = dfs(nextX, nextY, i);
if (i == d) //前进方向不变,到下一个节点的线段数量不变
{
seg[curX][curY][d] = min(seg[nextX][nextY][i], seg[curX][curY][d]);
}
else //前进方向改变,到下一个节点的线段数量+1
{
seg[curX][curY][d] = min(seg[nextX][nextY][i] + 1, seg[curX][curY][d]);
}
}
}

return seg[curX][curY][d];
}

int main()
{
//freopen("game.in", "r", stdin);

int T = 1; //第几组数据
while (cin >> w >> h)
{
if (w == 0 && h == 0)
{
break; //所有输入都结束了
}

memset(board, false, sizeof(board)); //初始化所有格子没有卡片

for (int i = 1; i <= h; i++) //行
{
cin.get(); //读取空格或回车或制表符

char c;
for (int j = 1; j <= w; j++) //列
{
cin.get(c);
if (c == 'X')
{
board[i][j] = true; //有卡片
}
}
}

cout << "Board #" << T << ":" << endl;

int group = 1; //第几组数据
while (cin >> startX >> startY >> endX >> endY)
{
if ((startX | endX | startY | endY) == 0)
{
break; //四个0表示本组测试结束
}

memset(seg, 0, sizeof(seg));

int ans = INF;
for (int i = 0; i < 4; i++)//四个方向
{
int nextX = startX + dir[2 * i];
int nextY = startY + dir[2 * i + 1];
int cnt = INF;
for (int j = 0; j < 4; j++)//四个方向
{
int temp = dfs(nextX, nextY, j);
if (i == j) //方向相同说明没有拐弯,线段数量不变
{
cnt = min(cnt, temp);
}
else //拐弯,线段数量增加
{
cnt = min(cnt, temp + 1);
}
}
ans = min(ans, cnt);
}
ans += 1; //在计算的过程中实际上差了1,因为第一次挪动时没有算

cout << "Pair " << group << ": ";
group++;

if (ans >= INF)
{
cout << "impossible." << endl;
}
else
{
cout << ans << " segments." << endl;
}
}//内层while结束

cout << endl;
T++; //下一轮游戏
}//外层while结束

return 0;
}