A - Maze
题意:
根据一张5×5的地图,起点为左上角,终点为右下角,输出最短路径。数据保证有唯一解。
思路:
这题是典型的迷宫问题。使用结构体数组记录每个点,利用bfs广度优先搜索,pre二维数组记录该点之前的点。最后使用递归思想寻找出路径。
总结:
结构体数组+bfs+路径记录,顺利解决问题。
代码:
#include <cstdio>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
struct Point
{
int x, y;
Point() {}
Point(int _x, int _y) : x(_x), y(_y) {}
bool operator==(const Point &t) const
{
return x == t.x && y == t.y;
}
};
const int maxn = 5;
bool vis[maxn][maxn];
int a[maxn][maxn], dis[maxn][maxn], n, m;
int sx, sy, tx, ty;
queue<Point> Q;
int pre[5][5];
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
void findthepath(int x, int y)
{
int px = pre[x][y] / 10, py = pre[x][y] % 10;
if (px == 0 && py == 0)
{
cout << "(" << px << ", " << py << ")" << endl;
cout << "(" << x << ", " << y << ")" << endl;
return;
}
findthepath(px, py);
cout << "(" << x << ", " << y << ")" << endl;
}
int bfs()
{
Q.push(Point(sx, sy));
memset(vis, 0, sizeof(vis));
memset(dis, 0, sizeof(dis));
vis[sx][sy] = true;
dis[sx][sy] = 0;
while (!Q.empty())
{
Point now = Q.front();
Q.pop();
for (int i = 0; i < 4; i++)
{
int x = now.x + dx[i], y = now.y + dy[i];
if (x >= 0 && x <= n && y >= 0 && y <= m && !vis[x][y] && !a[x][y])
{
dis[x][y] = dis[now.x][now.y] + 1;
pre[x][y] = now.x * 10 + now.y;//哈希方法
if (x == tx && y == ty)
return dis[tx][ty];
vis[x][y] = 1;
Q.push(Point(x, y));
}
}
}
return dis[tx][ty];
}
int main()
{
sx = sy = 0;
tx = ty = m = n = 4;
for (int i = 0; i <= 4; i++)
for (int j = 0; j <= 4; j++)
cin >> a[i][j];
bfs();
findthepath(tx, ty);
system("pause");
}
B - Pour Water
题意:
倒水问题 “fill A” 表示倒满A杯,"empty A"表示倒空A杯,“pour A B” 表示把A的水倒到B杯并且把B杯倒满或A倒空。
- Input
输入包含多组数据。每组数据输入 A, B, C 数据范围 0 < A <= B 、C <= B <=1000 、A和B互质。 - Output
你的程序的输出将由一系列的指令组成。这些输出行将导致任何一个罐子正好包含C单位的水。每组数据的最后一行输出应该是“success”。输出行从第1列开始,不应该有空行或任何尾随空格。
思路:
和经典的倒水问题有些区别,这里要求输出的不是中间结果,而是中间动作。这里有两种解决思路:
- 自设结构体,列出所有的状态,进行bfs,记录所有的中间态。
- 第一种方法的缺点就是需要列出所有状态,记录中间态也不方便,丧失了map的搜索元素,添加新元素等操作的便捷性。因此我在使用map<status,status>的基础上,新增了一个map<status,string>,用来记录中间倒水动作。每一次refresh的过程中,记录这个状态映射的动作。最后输出的时候,先根据map<status,status>找到上一个状态,再根据map<status,string>找到这个状态对应的倒水动作。这样的作法,基本没有改变经典倒水问题的代码,保留了map的高效。
总结:
经典倒水问题的思路就是使用map,利用bfs记录状态是否被访问过了。自认为新增map<status,string>记录中间动作是一种还不错的方法。
代码:
#include <cstdio>
#include <iostream>
#include <queue>
#include <map>
#include <cstring>
using namespace std;
struct Status
{
int a, b;
bool operator<(const Status &s) const
{
return a != s.a ? a < s.a : b < s.b;
}
};
queue<Status> Q;
map<Status, Status> from;
map<Status, string> pre;
/* 递归输出方案 */
void print(Status &p)
{
if (from.find(p) == from.end() || (p.a == 0 && p.b == 0))
return;
print(from[p]); // 递归
//printf("-><%d,%d>", p.a, p.b);
cout << pre[p].c_str() << endl;
}
void refresh(Status &s, Status &t, string ch)
{
if (from.find(t) == from.end())
{ // 特判合法,加入队列
from[t] = s;
pre[t] = ch;
Q.push(t);
}
}
bool bfs(int A, int B, int C)
{
// 起点, 两杯水都空
Status s, t;
s.a = 0;
s.b = 0;
Q.push(s);
while (!Q.empty())
{
// 取队首
s = Q.front();
Q.pop();
// 特判到达终点
if (s.a == C || s.b == C)
{
print(s); // 输出方案
return 1;
}
// 倒空 a 杯的水
if (s.a > 0)
{
t.a = 0; // 倒空
t.b = s.b; // b 杯不变
refresh(s, t, "empty A");
}
// 同理,倒空 b 杯的水
if (s.b > 0)
{
t.b = 0; // 倒空
t.a = s.a; // a 杯不变
refresh(s, t, "empty B");
}
// a 杯未满,续满 a 杯
if (s.a < A)
{
// 续满 a 杯
t.a = A;
t.b = s.b;
refresh(s, t, "fill A");
// 考虑倒入
if (s.b != 0)
{
if (s.a + s.b <= A)
{
t.a = s.a + s.b;
t.b = 0;
refresh(s, t, "pour B A");
}
else
{
t.a = A;
t.b = s.a + s.b - A;
refresh(s, t, "pour B A");
}
}
}
// 同理,b 杯未满,续满 b 杯
if (s.b < B)
{
t.a = s.a;
t.b = B;
refresh(s, t, "fill B");
if (s.a != 0)
{
if (s.a + s.b <= B)
{
t.a = 0;
t.b = s.a + s.b;
refresh(s, t, "pour A B");
}
else
{
t.a = s.a + s.b - B;
t.b = B;
refresh(s, t, "pour A B");
}
}
}
}
return -1;
}
int main()
{
int a, b, c;
while (scanf("%d%d%d", &a, &b, &c) == 3)
{
from.erase(from.begin(), from.end());
pre.erase(pre.begin(), pre.end());
while (!Q.empty())
Q.pop();
int flag = bfs(a, b, c);
if (flag == 1)
cout << "success" << endl;
}
//system("pause");
return 0;
} /* 26 29 11 */