A*与传教士和野人问题(M-C问题)


有N个传教士和N个野人来到河边渡河,河岸有一条船,每次至多可供k人乘渡。河两岸以及船上的野人数目总是不超过传教士的数目(否则不安全,传教士有可能被野人吃掉)。即求解传教士和野人从左岸全部摆渡到右岸的过程中,任何时刻满足M(传教士数)≥C(野人数)和M+C≤k的摆渡方案。以下讨论三个传教士三个野人还有一条船最多能载两个人的方案。

状态空间

我们用一个三元组(m,c,b)来表示河岸上的状态,其中m、c分别代表某一岸上传教士与野人的数目,b=1表示船在这一岸,b=0则表示船不在

约束条件是: 两岸上M≥C, 船上M+C≤2

(mi,ci)为船上的传教士和野人数量

左岸初态为(3,3,1),终态为(0,0,0)

image.png

为什么不能直接暴力穷举然后剪枝?

因为可能运过去两个人,然后又把同样的两个人运回来了(或者使用别的形式但是依旧是空转),所以要杜绝这种可能,当然最好的方法就是使用带有记忆的状态,如果之前遇到过这种状态那就拒绝执行return。

所以....如何实现去重的目标???

答:set<string/int>,我采取的是单个状态采用int,记录路径经过的状态采用string(中间用空格隔开)

#include<iostream>
#include<cstdio>
#include<map>
#include<string>
#include<set>
#include<vector>
using namespace std;
int N;
set<string>ans;
void print(vector<int>way){
    int i=0;
    string h;
    for(i=1;i<way.size();i++){
        printf("%03d   ",way[i]);
        if(way[i]<100){
            h+="0";
        }
        h+=to_string(way[i]);
        h+=" ";
    }
    cout<<"\n-------"<<endl;
    ans.insert(h);
}
void dfs(int pre,int ni,int nj,int c,set<int>s,vector<int>way){
    // set insert
    int now=ni*100+nj*10+c;
    s.insert(pre);    
    if(s.count(now)||(N-ni)<0||(N-nj)<0||ni<0||nj<0||(ni<nj&&ni!=0)||((N-ni)<(N-nj)&&(N-ni)!=0)){
        return;
    }
    if(pre==0){
        // 如果上一轮就截止了
        print(way);
        return;
    }
    way.push_back(pre);
    if(c==1){
        // zero people . no one on ship is illegal
        // dfs(now,ni,nj,0,s,way);
        // one people on
        dfs(now,ni-1,nj,0,s,way);
        dfs(now,ni,nj-1,0,s,way);
        // two people on
        dfs(now,ni-2,nj,0,s,way);
        dfs(now,ni,nj-2,0,s,way);
        dfs(now,ni-1,nj-1,0,s,way);
    }
    else{
        // zero people . no one on ship is illegal
        // dfs(now,ni,nj,1,s,way);
        // one people on
        dfs(now,ni+1,nj,1,s,way);
        dfs(now,ni,nj+1,1,s,way);
        // two people on
        dfs(now,ni+2,nj,1,s,way);
        dfs(now,ni,nj+2,1,s,way);
        dfs(now,ni+1,nj+1,1,s,way);        
    }
}
int main(){
    int chooseN;
    int c=1;
    // init num and ship side 1 means left and 0 means right
    int ni,nj;
    // Ni(传教士),Nj(野人) on the left side.
    int pre=-1;
    set<int>s;
    //cin>>chooseN;
    chooseN=3;
    N=nj=ni=chooseN;
    vector<int>way;
    dfs(pre,ni,nj,c,s,way);
    cout<<"\nfinally , the answer is:\n\n";
    for(auto it=ans.begin();it!=ans.end();it++){
        cout<<*it<<endl;
    }
}

/*
331   310   321   300   311   110   221   020   031   010   111   
-------
331   310   321   300   311   110   221   020   031   010   111   
-------
331   310   321   300   311   110   221   020   031   010   021   
-------
331   310   321   300   311   110   221   020   031   010   021
-------
331   220   321   300   311   110   221   020   031   010   111
-------
331   220   321   300   311   110   221   020   031   010   111   
-------
331   220   321   300   311   110   221   020   031   010   021
-------
331   220   321   300   311   110   221   020   031   010   021
-------

finally , the answer is:

331 220 321 300 311 110 221 020 031 010 021
331 220 321 300 311 110 221 020 031 010 111
331 310 321 300 311 110 221 020 031 010 021
331 310 321 300 311 110 221 020 031 010 111
*/

传说中的基于A*算法的解法:

评估函数的建立:

f=d+h=d+M+N-2*B

M 表示左岸的传教士的人数,N 表示左岸野人的数目,B 取值为0或1 ,1 表示船在左岸,0 表示船在右岸。d 表示节点的深度。

  • 下面我们来证明h(n)=M+C-2B是满足A*条件的:

我们分两种情况考虑。
(1)先考虑船在左岸的情况。如果不考虑限制条件,也就是说,船一次可以将三人从左岸运到右岸,然后再有一个人将船送回来。这样,船一个来回可以运过河2人,而船仍然在左岸。而最后剩下的三个人,则可以一次将他们全部从左岸运到右岸。所以,在不考虑限制条件的情况下,也至少需要摆渡[(M+N-3)/2]*2+1次。其中分子上的"-3"表示剩下三个留待最后一次运过去。除以"2"是因为一个来回可以运过去2人,需要[(M+N-3)/2]个来回,而"来回"数不能是小数,需要向上取整,这个用符号[ ]表示。而乘以"2"是因为一个来回相当于两次摆渡,所以要乘以2。而最后的"+1",则表示将剩下的3个运过去,需要一次摆渡。

化简得:需要 M+N-2次单向摆渡

(2)再考虑船在右岸的情况。同样不考虑限制条件。船在右岸,需要一个人将船运到左岸。因此对于状态(M,N,0)来说,其所需要的最少摆渡数,相当于船在左岸时状态(M+1,N,1)或(M,N+1,1)所需要的最少摆渡数,再加上第一次将船从右岸送到左岸的一次摆渡数。因此所需要的最少摆渡数为:(M+N+1)-2+1。其中(M+N+1)的"+1"表示送船回到左岸的那个人,而最后边的"+1",表示送船到左岸时的一次摆渡。

化简有:(M+N+1)-2+1=M+N。

综合船在左岸和船在右岸两种情况下,所需要的最少摆渡次数用一个式子表示为:M+N-2B ,其中B=1表示船在左岸,B=0表示船在右岸。该摆渡次数是在不考虑限制条件下,推出的最少所需要的摆渡次数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354