人狼羊菜过河 题解

题目: 

L2-3 人狼羊菜过河(*) 

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

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

用 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

注:为使输出的解法排列顺序一致,要求人每次渡河时都按以下顺序进行探索:

  • 直接过河(不带任何东西)
  • 带狼过河
  • 带羊过河
  • 带菜过河

 

题意:

       在人不在时,狼会吃羊,羊会吃菜;题目给出初始和最终的河岸两边(人狼羊菜)的分布情况S1,S2,问在安全的情况下,输出S1到S2的全部解法(输出全部解法时要求人每次渡河时都按以上题目中规定顺序输出)。

思路:

       该题目我们可以将其转化为:可能出现的每一种状态记作结点,存在某两个状态可转移关系(安全情况下)记作边,建图,求S1结点到S2结点的所有简单路径问题。

        首先我们以左岸边为参考系,人与船可视为一体,当某一物种在左岸记为1,反之即为0;如此即可将所有分布情况通过四位二进制数XXXX(X=0,1)表示当前状态(结点)。当然还需对每一种状态进行检查,判断该状态是否合法,如果合法则push到结点集e中;

int check1(int i){
    // 人在的一岸安全,故检查另一岸是否合理;
    if((i&0b1000) != 0) i ^= 0b1111; // 取反;
    if((i & (i<<1)) != 0) return 0; // 狼羊 || 羊菜;
    return 1;
}

         第二步就是遍历结点集中每个结点i(结点编号即为二进制数值),编写函数判断结点i与其他结点是否有转移关系,例如:状态1111与1101和0101与1101都存在转移关系,前者是人带着羊到河对岸,后者是人单独从对岸过来;如果存在两结点i,j有转移关系,即可将边<i, j>push到边集中。

// 判断状态i,j是否可互相转移;
int check2(int i, int j, int &temp){ // temp为边权;
    if(i == j) return 0;
    if(i < j) swap(i, j); 
    // 状态转移,人从一岸转移到另一岸;
    if(((i>>3) ^ (j>>3)) == 0) return 0; // 当i,j状态都为1xxx,1xxx则不存在转移关系;
    // 人最多只能带一个东西;
    i %= 8; j %= 8; // 1xxx -> 0xxx;
    if(i == j){ temp = 3; return 1;} // 人单独过河;
    if(__builtin_popcount(i^j) == 1){ // 人带一个物种过河(__builtin_popcount内置函数,可以求二进制数中有多少个1);
        if((i^j) == 0b100) temp = 2; // 带狼过河;
        if((i^j) == 0b10) temp = 1; // 带羊过河;
        if((i^j) == 0b1) temp = 0; // 带菜过河;
        return 1;
    }
    return 0;
}

         最后就是建图求两点所有简单路径了,建图方式这里采用链式前向星的数据结构,题目中特意强调了探索顺序是人单独过河、带狼、带羊、带菜的探索顺序优先输出,所以每次从单独过河的边开始遍历以此类推,故在边集中给每个边设置边权,再根据边权从小到大排序每个边,将边权较小的边先加入到图中,如此通过这个方法来控制遍历结点的边的顺序(感觉这个方法有点呆)。建完图后通过dfs深度优先遍历去寻找所有的S1到S2的简单路径并按相应的格式输出即可。

vector<int> temp_path;
vector<int> vis;
void dfs(int u, int v, vector<vector<int> > &path){
    temp_path[++index] = u;
    // 当u到达v保存路径;
    if(u == v){
        temp_path[0] = index--; // temp_path[0]来保存该条路径有多少结点;
        path.push_back(temp_path);
        return;
    }
    vis[u] = 1; // 标记经过的路径;
    for(int i = head[u];i;i = edge[i].next){
        int to = edge[i].v;
        if(vis[to]) continue;
        dfs(to, v, path);
    }
    vis[u] = 0; index--;
}

         大致思路就是这样,本人萌新小白一枚૧(●´৺`●)૭,解题思路和代码有不足的地方还请各位大佬指点,最后献上源码。

 AC源码:

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define int long long
#define endl '\n'
typedef struct node{
    int u, v, w;
    bool operator < (const node &n) const{
        return w < n.w;
    };
}Node;
typedef struct MP {
    int num = 0, index = 0;
    int head[20];
    vector<int> temp_path;
    vector<int> vis;
    struct edge{
        int next, v;
    }edge[4000];

    void Init(){
        temp_path = vector<int>(20, 0);
        vis = vector<int>(20, 0);
        memset(head, 0, sizeof(head));
    }

    void add(int u, int v){
        edge[++num].next = head[u];
        edge[num].v = v;
        head[u] = num;
    }

    void dfs(int u, int v, vector<vector<int> > &path){
        temp_path[++index] = u;
        // 当u到达v保存路径;
        if(u == v){
            temp_path[0] = index--;
            path.push_back(temp_path);
            return;
        }
        vis[u] = 1; // 标记经过的路径;
        for(int i = head[u];i;i = edge[i].next){
            int to = edge[i].v;
            if(vis[to]) continue;
            dfs(to, v, path);
        }
        vis[u] = 0; index--;
    }
}MP;
MP mp;

int cnt = 0;
int sta[20]; // 左岸为参考系,在左岸为1,反之为0,对应的合法状态;
// 判断状态i是否合法;
int check1(int i){
    // 人在的一岸安全,故检查另一岸是否合理;
    if((i&0b1000) != 0) i ^= 0b1111;
    if((i & (i<<1)) != 0) return 0; // 狼羊 || 羊菜;
    return 1;
}
// 判断状态i,j是否可互相转移;
int check2(int i, int j, int &temp){
    if(i == j) return 0;
    if(i < j) swap(i, j);
    // 状态转移,人从一岸转移到另一岸;
    if(((i>>3) ^ (j>>3)) == 0) return 0;
    // 人最多只能带一个东西;
    i %= 8; j %= 8;
    if(i == j){ temp = 3; return 1;}
    if(__builtin_popcount(i^j) == 1){
        if((i^j) == 0b100) temp = 2;
        if((i^j) == 0b10) temp = 1;
        if((i^j) == 0b1) temp = 0;
        return 1;
    }
    return 0;
}

void Init(){
    // 获取所有合法状态;
    for(int i = 0b1111;i;i = (i-1)&0b1111) 
        if(check1(i)) sta[++cnt] = i;
    sta[++cnt] = 0; // 状态0000合法;
    // 获取所有合法状态中,可互相转移的状态对<s1, s2>;
    vector<Node> v;
    for(int i = 1;i <= cnt;i++){
        for(int j = 1;j <= cnt;j++){
            int temp = 0;
            if(!check2(sta[i], sta[j], temp)) continue;
            v.push_back({sta[i], sta[j], temp});
        }
    }
    // 以状态为结点,可转移关系为边建图;
    sort(v.begin(), v.end()); mp.Init();
    for(auto e : v) mp.add(e.u, e.v);
}

// 打印i状态信息;
char name[4] = {'M', 'W', 'G', 'C'};
string print(int i){
    string ret = "";
    int temp = i;
    for(int j = 3;j >= 0;j--){
        if(temp & 1) ret = name[j] + ret;
        else ret = '.' + ret;
        temp >>= 1;
    }
    if(i & 0b1000) ret += " -> ";
    else ret += " <- ";
    
    temp = (i ^ 0b1111); string ret0 = "";
    for(int j = 3;j >= 0;j--){
        if(temp & 1) ret0 = name[j] + ret0;
        else ret0 = '.' + ret0;
        temp >>= 1;
    }
    return ret + ret0;
}

void solve(){
    int u = 0, v = 0;
    string temp1, temp2; 
    getline(cin, temp1); getline(cin, temp2);
    for(int i = 0;i < 4;i++){ // 获取初始和终止状态S1,S2
        u <<= 1; v <<= 1;
        u += temp1[i] == '.' ? 0 : 1;
        v += temp2[i] == '.' ? 0 : 1;
    }
    vector<vector<int> > path; // S1到S2所有的简单路径;
    mp.dfs(u, v, path);
    if(path.size() == 0 || u == v){ // S1和S2状态不能相同;
        cout << "None" << endl;
        return;
    }
    for(int i = 0;i < path.size();i++){
        for(int j = 1;j <= path[i][0];j++){
            cout << print(path[i][j]) << endl;
        }
        if(i != path.size()-1) cout << endl;
    }
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    Init();
    solve();
    
    return 0;
}

        

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值