妖怪与和尚过河问题

问题描述:

有三个和尚和三个妖怪,他们要利用唯一一条小船过河,这条小船一次最多只能载两个人,同时,无论是在河的两岸还是船上,只要妖怪的数量大于和尚的数量,妖怪们就会将和尚吃掉。现在需要选择一种过河的安排,保证和尚和妖怪都能过河且和尚不能被妖怪吃掉。

问题分析:

将和尚和妖怪们的状态抽象为一个数组

$status = array(
  ‘left_missionary_num‘,  //左岸和尚数量
  ‘left_monster_num‘,    //左岸妖怪数量
  ‘right_missionary_num‘,  //右岸和尚数量
  ‘right_monster_num‘    //右岸妖怪数量
)

初始状态为array(3,3,0,0),并且船在左岸,最终状态是array(0,0,3,3),此时船在右岸。

船的左右移动会推动状态的转变,用-1表示船在左岸,用1表示船在右岸,当船靠岸时,船上的所有和尚或者妖怪要下船。

为了保证和尚不被妖怪吃掉,则需要满足以下约束

$status[‘left_missionary_num‘] >= $status[‘left_monster_num‘],表示左岸和尚的数量>=左岸妖怪的数量
$status[‘right_missionary_num‘] >= $status[‘right_monster_num‘],表示右岸和尚的数量>=右岸妖怪的数量
3 - $status[‘left_missionary_num‘] - $status[‘right_missionary_num‘] >= 3 - $status[‘left_monster_num‘] - $status[‘right_monster_num‘],表示船上的和尚数量>=船上的妖怪数量,因为船上最多只有2个位置,所以这个条件是默认满足的。

状态的搜索:

每一次状态转变时:
上船:要根据船的位置来判断是左岸还是右岸的和尚或妖怪上船。
下船:要根据船的位置来判断船上的和尚或妖怪下船后会加入左岸还是右岸。

下船后,要符合上面提到的约束。

并且上船后,要保证岸边的和尚或者妖怪的数量都要 >= 0。

每个状态可能会有多个下一个状态,在不考虑约束的情况下,总共会有5种可能,分别是①1个和尚上船、②2个和尚上船、③1个妖怪上船、④2个妖怪上船,⑤一个和尚与一个妖怪上船。
对上述5种可能逐一进行约束验证,如果验证不通过,则忽略这种可能。

避免陷入"环路":

在搜索状态时,会遇到“状态环路”,例如array(3,3,0,0) -> array(3,2,0,1) -> array(3,3,0,0),有一些状态环路包含的步骤可能会更多一些。如果对这种情况不做处理,搜索就会陷入循环从而无法得出结果。

如何避免“状态环路”:

首先定义状态的数据结构:

$status = array(
    ‘id‘,
    ‘value‘,
    ‘ancestor_ids‘,
    ‘boat_status‘
);

$status包含4个属性:id、value、ancestor_ids、boat_status,其中,每一个$status的id都是唯一的(id >= 0)(只有初始状态的id = 0),value是一个数组,代表状态的值,例如array(3,3,0,0),ancestor_ids也是一个数组,包含了(本状态的)所有父、祖父、。。。、状态的id,boat_status代表船的位置。

当一个状态和它的ancestor_ids中包含的状态中的任意一个的value和boat_status完全一致时,就可以判定这个状态前面已经出现过了,直接忽略它就可避免“状态环路”。

问题解答:

总共有4种不同的安排可以让和尚和妖怪安全地过河,这4种安排所使用的步骤数目都是一样的,都是11步。

程序设计(PHP):

  1 <?php
  2 echo ‘<p>问题描述:</p>‘;
  3 echo ‘<p>有三个和尚和三个妖怪,他们要利用唯一一条小船过河,这条小船一次最多只能载两个人,同时,无论是在河的两岸还是船上,只要妖怪的数量大于和尚的数量,妖怪们就会将和尚吃掉。现在需要选择一种过河的安排,保证和尚和妖怪都能过河且和尚不能被妖怪吃掉。</p>‘;
  4 echo ‘<p>解答:</p>‘;
  5 echo ‘<p>其中,[]括号中数字的含义是:第一个代表左岸和尚的数量,
  6 第二个代表左岸妖怪的数量,第三个代表右岸和尚的数量,第四个代表右岸妖怪的数量;boat status等于-1代表船在左岸,boat status等于1代表船在右岸。</p>‘;
  7
  8 echo "<pre>";
  9
 10 const left_missionary_num   = 0;
 11 const left_monster_num      = 1;
 12 const right_missionary_num  = 2;
 13 const right_monster_num     = 3;
 14
 15 const boat_missionary_num   = 0;
 16 const boat_monster_num      = 1;
 17
 18 $currentStatus = array(
 19     left_missionary_num => 3,
 20     left_monster_num    => 3,
 21     right_missionary_num=> 0,
 22     right_monster_num   => 0
 23 );
 24
 25 $boat = -1;//left bank
 26 $statusId = 0;
 27 $statuses = array(
 28     0 => array(
 29         ‘value‘         => $currentStatus,
 30         ‘ancestor_ids‘  => array(),
 31         ‘boat_status‘   => -1
 32     )
 33 );
 34
 35 findPath($currentStatus, $statusId, $statusId, $statuses, $boat);
 36
 37 echo ‘<p>搜索中产生的所有状态(状态树):</p>‘;
 38 print_r($statuses);
 39
 40
 41
 42 function findPath(array $status, $id, &$statusId, &$statuses, $boat)
 43 {
 44     $availableBoatStatuses = array(
 45         array(boat_missionary_num => 1, boat_monster_num => 0),
 46         array(boat_missionary_num => 2, boat_monster_num => 0),
 47         array(boat_missionary_num => 0, boat_monster_num => 1),
 48         array(boat_missionary_num => 0, boat_monster_num => 2),
 49         array(boat_missionary_num => 1, boat_monster_num => 1),
 50     );
 51     foreach ($availableBoatStatuses as $availableBoatStatus) {
 52         $nextStatus = generateNextStatus($status, $availableBoatStatus, $boat);
 53
 54         if($nextStatus){
 55             $_boat = -1 * $boat;
 56             $ancestorIds = $statuses[$id][‘ancestor_ids‘];
 57             $ancestorIds[] = $id;
 58
 59             $isAncestor = false;
 60             foreach ($ancestorIds as $ancestorId) {
 61                 if($statuses[$ancestorId][‘value‘] ===  $nextStatus
 62                     && $_boat === $statuses[$ancestorId][‘boat_status‘]
 63                 ){
 64                     $isAncestor = true;
 65                     break;
 66                 }
 67             }
 68
 69             if($isAncestor){
 70                 continue;   //Ignore the result if $nextStatus === $statuses[‘$ancestorId‘]
 71             }
 72
 73             $statusId++;
 74
 75             $statuses[$statusId] = array(
 76                 ‘value‘ => $nextStatus,
 77                 ‘ancestor_ids‘  => $ancestorIds,
 78                 ‘boat_status‘   => $_boat
 79             );
 80
 81             if($nextStatus === array(0,0,3,3)){
 82
 83                 foreach ($statuses[$statusId][‘ancestor_ids‘] as $key => $ancestorId) {
 84                     //print_r($statuses[$ancestorId][‘value‘]);
 85                     if($key % 5 == 0){
 86                         echo ‘<br/>‘;
 87                     }
 88                     $v = $statuses[$ancestorId][‘value‘];
 89                     printf(‘[%d,%d,%d,%d], boat status = %d -->‘, $v[0],$v[1],$v[2],$v[3],$statuses[$ancestorId][‘boat_status‘]);
 90                 }
 91                 printf(‘[%d, %d, %d, %d], boat status = %d‘, $nextStatus[0],$nextStatus[1],$nextStatus[2],$nextStatus[3],$_boat);
 92                 echo ‘<br/>‘;
 93
 94                 printf(‘总共使用 %d 步完成‘, count($statuses[$statusId][‘ancestor_ids‘]));
 95                 echo ‘<hr>‘;
 96             }else{
 97                 findPath($nextStatus, $statusId, $statusId, $statuses, $_boat);
 98             }
 99         }
100     }
101 }
102
103 /**
104  * @param array $status
105  * @param array $availableBoatStatus
106  * @param int $boatStatus
107  * @return array|bool
108  */
109 function generateNextStatus(array $status, array $availableBoatStatus, $boatStatus)
110 {
111     if($boatStatus < 0){
112         //船在左岸,上船后
113         $status[left_missionary_num] = $status[left_missionary_num] - $availableBoatStatus[boat_missionary_num];
114         $status[left_monster_num] = $status[left_monster_num] - $availableBoatStatus[boat_monster_num];
115         //船到右岸,下船后
116         $status[right_missionary_num] = $status[right_missionary_num] + $availableBoatStatus[boat_missionary_num];
117         $status[right_monster_num] = $status[right_monster_num] + $availableBoatStatus[boat_monster_num];
118     }else{
119         //船在右岸,上船后
120         $status[right_missionary_num] = $status[right_missionary_num] - $availableBoatStatus[boat_missionary_num];
121         $status[right_monster_num] = $status[right_monster_num] - $availableBoatStatus[boat_monster_num];
122         //船到左岸,下船后
123         $status[left_missionary_num] = $status[left_missionary_num] + $availableBoatStatus[boat_missionary_num];
124         $status[left_monster_num] = $status[left_monster_num] + $availableBoatStatus[boat_monster_num];
125     }
126
127     foreach ($status as $value) {
128         if($value < 0){
129             return false;
130         }
131     }
132
133     if(($status[left_missionary_num] >= $status[left_monster_num]) &&
134         ($status[right_missionary_num] >= $status[right_monster_num])
135         || $status[left_missionary_num] === 0 || $status[right_missionary_num] === 0){
136         return $status;
137     }
138
139     return false;
140 }
时间: 2024-10-14 03:26:19

妖怪与和尚过河问题的相关文章

过河问题(牛虎过河、商人仆人过河、农夫妖怪过河、传教士野人过河)(第2届第2题)

题目要求 问题描述:三只牛三只虎过河,船最多只能容纳两只动物,且船在往返途中不能为空.在任一岸边,若牛的数量少于虎的数量,则牛就会被老虎吃掉.为了使动物全部过河且使无损失,请制定合理的渡河方案. 解决方案 这也是一个经典的渡河问题了,由此衍化出的版本有商人仆人(随从)过河,农夫妖怪过河,传教士野人过河...除了角色有变化,内容本质上是一样的. 假设原来的动物和船都在A岸,现在想渡河到对面的B岸.考虑牛虎数量和船的位置,可以将本题中的所有可能出现的情形描述为静态属性和动态属性.静态属性就是船停靠在

洛谷 【P1052】过河

P1052 过河 题目描述 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧.在桥上有一些石子,青蛙很讨厌踩在这些石子上.由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,……,L(其中L是桥的长度).坐标为0的点表示桥的起点,坐标为L的点表示桥的终点.青蛙从桥的起点开始,不停的向终点方向跳跃.一次跳跃的距离是S到T之间的任意正整数(包括S,T).当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥. 题目给出独木桥的长度

贪心—— P1809 过河问题_NOI导刊2011提高(01)

洛谷——P1809 过河问题_NOI导刊2011提高(01) 题目描述 有一个大晴天,Oliver与同学们一共N人出游,他们走到一条河的东岸边,想要过河到西岸.而东岸边有一条小船. 船太小了,一次只能乘坐两人.每个人都有一个渡河时间T,船划到对岸的时间等于船上渡河时间较长的人所用时间. 现在已知N个人的渡河时间T,Oliver想要你告诉他,他们最少要花费多少时间,才能使所有人都过河. 注意,只有船在东岸(西岸)的人才能坐上船划到对岸. 输入输出格式 输入格式: 输入文件第一行为人数N,以下有N行

马拦过河卒心得体会

题目棋盘上A点有一个过河卒,需要走到目标B点.卒行走的规则:可以向下.或者向右.同时在棋盘上C点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点.因此称之为"马拦过河卒". 棋盘用坐标表示,A点(0, 0).B点(n, m)(n, m为不超过15的整数),同样马的位置坐标是需要给出的.现在要求你计算出卒从A点能够到达B点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步. 输入一行四个数据,分别表示B点坐标和马的坐标.(保证所有的数据有解) 输出一个数据

NOIP2005 过河

描述 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧.在桥上有一些石子,青蛙很讨厌踩在这些石子上.由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,……,L(其中L是桥的长度).坐标为0的点表示桥的起点,坐标为L的点表示桥的终点.青蛙从桥的起点开始,不停的向终点方向跳跃.一次跳跃的距离是S到T之间的任意正整数(包括S,T).当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥. 题目给出独木桥的长度L,青蛙跳跃的距离范围

hdu 4842(NOIP 2005 过河)之 动态规划(距离压缩)

/* hdu 4842(NOIP 2005 过河)之 动态规划(距离压缩) dp[i] := 到桥上的i位置,最少需要踩到的石子数. dp[i] = min(dp[i], dp[i-j] + isStone[i])    条件: (1) S<=j<=T (2) dp[i-j]是青蛙可以到达的点 独木桥长度L,最大为10^9,直接用转移方程,TLE:由于桥上的石子个数100,所以当石子分布在独木桥上时,是很稀疏的. (1)S!=T 考虑,对于此题关键在于求踩到的石子数,而非跳的次数,对于两个相邻

商人过河问题(DFS)

问题描述:3个商人带着3个仆人过河,过河的工具只有一艘小船,只能同时载两个人过河,包括划船的人.在河的任何一边,只要仆人的数量超过商人的数量,仆人就会联合起来将商人杀死并抢夺其财物,问商人应如何设计过河顺序才能让所有人都安全地过到河的另一边. 详细过程参见<数学模型>第四版(姜启源) #include <cstdio> #define maxn 101 int num;//number of bus or fol int graph[maxn*maxn][maxn*maxn]; i

【水】noip2002普及 过河卒

这题似乎是当年的马拦过河卒,好久的回忆啊... 过河卒 来源 NOIP2002普及组 题目描述 如图,A 点有一个过河卒,需要走到目标 B 点.卒行走规则:可以向下.或者向右.同时在棋盘上的任一点有一个对方的马(如上图的C点),该马所在的点和所有跳跃一步可达的点称为对方马的控制点.例 如上图 C 点上的马可以控制 9 个点(图中的P1,P2 … P8 和 C).卒不能通过对方马的控制点. 棋盘用坐标表示,A 点(0,0).B 点(n,m)(n,m 为不超过 25 的整数,并由键盘输入),同样马的

shu_1548 悟空的难题(大师兄,师傅被妖怪抓走啦!)

http://202.121.199.212/JudgeOnline/problem.php?cid=1078&pid=17 分析:  直接暴力了... 代码: #include <stdio.h> #include <iostream> using namespace std; #define MAXN 2004 #define inf 0x3f3f3f3f int k[MAXN],f[MAXN]; int my_abs(int a) { return a<0? -