作业笔记:(PTA) 人狼羊菜过河

题目:

一个人要将一匹狼、一只羊、一筐菜运到河对岸,但是他的船太小了,一次只能带一样过河。当他不在时,狼要吃羊、羊要吃菜。他怎样才能安全地将狼、羊、菜都运过河呢?

题图.jpg

请编写程序,找出过河的全部方案。

用 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;
}

本题的繁琐之处主要在于不同状态的表示以及部分边界条件的验证,要注意输出格式的处理和一系列条件判断语句的顺序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值