题目:
一个人要将一匹狼、一只羊、一筐菜运到河对岸,但是他的船太小了,一次只能带一样过河。当他不在时,狼要吃羊、羊要吃菜。他怎样才能安全地将狼、羊、菜都运过河呢?
请编写程序,找出过河的全部方案。
用 4 个字母分别表示人、狼、羊、菜的状态:
- 用 M 表示人在,用 . 表示人不在
- 用 W 表示狼在,用 . 表示狼不在
- 用 G 表示羊在,用 . 表示羊不在
- 用 C 表示菜在,用 . 表示菜不在
- 用 -> 表示船将从左岸去右岸,用 <- 表示船将从右岸去左岸。
举例说明:
若人狼羊菜全在左岸,则状态表示为
MWGC -> ....
若人将羊运到右岸,左岸只剩狼和菜,则状态表示为
.W.C <- M.G.
输入格式
初始状态
终止状态
输出格式
若有解,则输出所有解法。
每一个解法由若干行组成,每行表示一个状态。
不同的解法之间空一行。
若无解,则输出 None。
要求:输出的解法中不允许出现两行相同的状态。
输入样例1
MWGC -> ....
.... <- MWGC
输出样例1
MWGC -> ....
.W.C <- M.G.
MW.C -> ..G.
...C <- MWG.
M.GC -> .W..
..G. <- MW.C
M.G. -> .W.C
.... <- MWGC
MWGC -> ....
.W.C <- M.G.
MW.C -> ..G.
.W.. <- M.GC
MWG. -> ...C
..G. <- MW.C
M.G. -> .W.C
.... <- MWGC
输入样例2
.W.. <- M.GC
M.GC -> .W..
输出样例2
.W.. <- M.GC
MWG. -> ...C
..G. <- MW.C
M.GC -> .W..
.W.. <- M.GC
MW.C -> ..G.
...C <- MWG.
M.GC -> .W..
输入样例3
MWG. -> ...C
MWG. -> ...C
输出样例3
None
输入样例4
MWG. -> ...C
.WG. -> M..C
输出样例4
None
注:为使输出的解法排列顺序一致,要求人每次渡河时都按以下顺序进行探索:
- 直接过河(不带任何东西)
- 带狼过河
- 带羊过河
- 带菜过河
题解:
主要思路:通过二进制编码,将一个int类型的整数作为数据结构来实现状态的记录和转移。对于人,狼,羊,和菜,都分别用对应位置的0和1来代表他们在左岸还是右岸。
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int ways[4] = { 8, 12, 10, 9 };
// 二进制分别是1000, 1100, 1010, 1001, 对应题目要求的四种过河方式
int state, ending, go_left, start_drct, end_drct, has_answer;
string items = "CGWM";
vector<int> ans;
void explain(vector<int> ans){ // 对ans中的整数作解释输出
if(has_answer) cout << "\n";
has_answer = 1;
int tmp = go_left;
for(auto step : ans){
for(int i = 3; i >= 0; i--){
if(step & (1 << i)) cout << ".";
else cout << items[i];
}
if(go_left) cout << " <- ";
else cout << " -> ";
go_left = !go_left;
for(int i = 3; i >= 0; i--){
if(step & (1 << i)) cout << items[i];
else cout << ".";
}
cout << "\n";
}
go_left = tmp;
}
void DFS(int last) // last传递上次的过河方式
{
if (state == 3 || state == 6 || state == 7 || state == 8 || state == 9 || state == 12) // 0011, 0110, 0111, 1000, 1001, 1100, 对应6种狼和羊独处,羊和菜独处的情况
return;
if (state == ending && (ans.size() & 1) == (start_drct == end_drct)) {
// 验证当前状态是否和结束状态相同的同时通过答案的个数检查箭头是否匹配
explain(ans);
return;
}
for (int i = 0; i < 4; i++) {
if(i == last) continue;
// 如果当前的过河方式和上次相同, 则又回到了上次过河前的状态,所以跳过本轮循环
state = state ^ ways[i]; //通过异或运算实现过河方式的模拟
if(find(ans.begin(), ans.end(), state) == ans.end()){
ans.push_back(state);
DFS(i);
ans.pop_back();
}
state = state ^ ways[i]; // 再做一次异或运算回溯到过河前的状态
}
}
int main()
{
string str;
getline(cin, str);
for (int i = 0; i < 4; i++) {
if (str[i] == '.')
state = state | (1 << (3 - i));
}
if(str[5] == '<') {
start_drct = 1;
go_left = !go_left;
}
getline(cin, str);
for (int i = 0; i < 4; i++) {
if (str[i] == '.')
ending = ending | (1 << (3 - i));
}
if(str[5] == '<') end_drct = 1;
ans.push_back(state);
if(state != ending) DFS(-1);
// -1为随便取的一个负值, 这是因为第一次过河没有“上一次的过河方式”, 所以随便取一个负值保证每种方法都不会被跳过
if(!has_answer) cout << "None";
return 0;
}
本题的繁琐之处主要在于不同状态的表示以及部分边界条件的验证,要注意输出格式的处理和一系列条件判断语句的顺序。