GitHub - jzplp/aoapc-UVA-Answer: 算法竞赛入门经典 例题和习题答案 刘汝佳 第二版
这个题目并没有规定最长多少步数就停止,而是当不可能走到时输出-1。因此没办法用迭代加深搜索的方式。
由于只有15个结点,石头本身也没有区分编号,因此场景有限,于是判断需要记录搜索过的状态。这里可以用二进制的方式记录15位二进制,0表示没有石头,1表示有石头。然后再用一个最大15的数字表示机器所在位置即可。这样用一个数组即可表示是否访问过。
一开始的超时做法:需要判断"不可能到达",因此需要走过所有的状态。于是我用DFS遍历所有状态,设置访问过的记录。DFS在遍历的过程中可以同一个全局数组记录路径。但是这样拿不到"最优"的结果,因为有些状态虽然访问过了,但并不是以最佳步数访问的,下次以更短步数访问时,却又被排除了。如果访问数组增加"到达状态时的步数"记录,如果小于记录的步数,则可以继续遍历。这样可以拿到正确的结果,但是超时。
后来查看了网上的思路,改用了BFS遍历。BFS是先遍历完低步数,再尝试高步数的,因此只要拿到结果时,就保证结果的步数是最小的。而且访问到某个状态时,可以直接记录下是否访问过,只要访问过就是最佳访问次数,下次访问时直接跳过即可。这样就避免了DFS的超时问题了。不过BFS我这里记录的状态比较多。
AC代码
cpp
#include <stdio.h>
#include <string.h>
#include <queue>
#include <vector>
using namespace std;
int n, m, s, t;
vector<int> graph[20];
// 0 空白 1 石头 2 机器人
int vertices[20];
bool foundFlag;
int paths[400000];
struct Path
{
bool has; // 是否已经访问过
int preNode; // 上一个节点的二进制值
int preRobot; // 上一个节点的机器位置
int stepBeg; // 这一步起点
int stepEnd; // 这一步终点
};
struct Node
{
int node; // 节点的二进制值
int robot; // 节点的机器位置
int stepBeg; // 这一步起点
int stepEnd; // 这一步终点
int num; // 当前步数
int preNode; // 上一个节点的二进制值
int preRobot; // 上一个节点的机器位置
};
queue<Node> qu;
// 是否访问过这个状态
Path mp[40000][16];
void setPaths(Node no)
{
int i, j, k;
Path p = mp[no.node][no.robot];
for (i = no.num - 1; i >= 0; --i)
{
paths[i] = p.stepBeg + p.stepEnd * 20;
p = mp[p.preNode][p.preRobot];
}
}
int setRawVertices()
{
int num = 0;
for (int i = 0; i < n; ++i)
{
if (vertices[i] == 1)
num = num * 2 + 1;
else
num = num * 2;
}
return num;
}
void getRawVertices(int node, int robot)
{
int a, b;
for (int i = n - 1; i >= 0; --i)
{
a = node % 2;
node = node / 2;
vertices[i] = a;
}
vertices[robot] = 2;
}
Node bfs()
{
int i, j, jp, k;
Node no = {setRawVertices(), s, 0, 0, 0, 0, 0};
qu.push(no);
while (qu.size())
{
no = qu.front();
qu.pop();
if (mp[no.node][no.robot].has)
continue;
mp[no.node][no.robot] = {true, no.preNode, no.preRobot, no.stepBeg, no.stepEnd};
if (no.robot == t)
{
foundFlag = true;
return no;
}
getRawVertices(no.node, no.robot);
for (i = 0; i < n; ++i)
{
if (!vertices[i])
continue;
for (jp = 0; jp < graph[i].size(); ++jp)
{
j = graph[i][jp];
if (vertices[j])
continue;
vertices[j] = vertices[i];
vertices[i] = 0;
Node newNode = {
setRawVertices(),
(i == no.robot ? j : no.robot),
i, j, no.num + 1, no.node, no.robot};
qu.push(newNode);
// 改回去
vertices[i] = vertices[j];
vertices[j] = 0;
}
}
}
return {};
}
int main()
{
int T, i, j, k = 0, a, b;
scanf("%d", &T);
while (++k <= T)
{
for (i = 0; i < 20; ++i)
graph[i].clear();
queue<Node>().swap(qu);
memset(vertices, 0, sizeof(vertices));
memset(mp, 0, sizeof(mp));
memset(paths, 0, sizeof(paths));
foundFlag = false;
scanf("%d %d %d %d", &n, &m, &s, &t);
s = s - 1;
t = t - 1;
for (i = 0; i < m; ++i)
{
scanf("%d", &a);
vertices[a - 1] = 1;
}
vertices[s] = 2;
for (i = 1; i < n; ++i)
{
scanf("%d %d", &a, &b);
a = a - 1;
b = b - 1;
graph[a].push_back(b);
graph[b].push_back(a);
}
if (s == t)
{
printf("Case %d: 0\n\n", k);
continue;
}
Node no = bfs();
if (!foundFlag)
{
printf("Case %d: -1\n\n", k);
continue;
}
setPaths(no);
printf("Case %d: %d\n", k, no.num);
for (i = 0; i < no.num; ++i)
printf("%d %d\n", paths[i] % 20 + 1, paths[i] / 20 + 1);
putchar('\n');
}
return 0;
}