“三桶水”问题答案扩展

CoderPlusPlus 2010-08-06 11:47:38
今天在社区看到有人问“三桶水”问题传送门

关于这类问题中,两个小桶容量之和等于大桶容量的情况,小时候在一本名为《使人聪明的数学智力游戏》的书上看到一个很巧妙的解法,至今记忆犹新,顺手做了个程序来演示解法

这个问题的分析过程如下

首先建立这样一个特殊的坐标系,图中的每一个交点就可以表示三个水桶的一种状态,具体对应方式是,对于任意一个交点,水平方向对应左边的坐标值表示A桶(容量为13)当前水量,沿斜线向右上方对应上面坐标轴的值为B桶(容量为17)当前水量,沿斜线向右下方对应右下坐标值表示C桶(容量为30)当前水量

下图中左下方的圆圈处就表示初始状态(0,0,30)


下面研究倒水过程中状态的变化,注意以下几条规则:

1、每次倒水操作中总有一个桶没有动,体现到图中就是每次倒水前后两个点的连线必定是图中某条线段,也就是连线必然平行于三条坐标轴中某一条

2、每次倒水必有一个桶被倒满或者倒空,体现到图中就是倒水过程中出现的状态必然在平行四边形的外围四条边上

于是,问题就转化为根据上述两条规则使得初始状态转化到(0,15,15)的状态

下图表示了开始是可能的几步,从中不难看出规律



只按照上面两条规则,每到达一个点都有最多四条后续方案可选,接下来就来进一步明确选择的方案

当第二步从(0, 17, 13)到达(13,4,13)后,接下来可以有4条合法路径,首先排除原路返回到(0,17,13),剩下的有(0,4,26)、(13,0,17)、(13、17、0)三种选择,注意到顶点的状态有这样的一个特点,任何一步接下来都可以直接到达至少两个顶点,而从初始状态最多只需要两步就可以到达每一个顶点,所以如果中途再到达顶点的话相当于前面的步骤都做了无用功,因此,除了第一步不可避免的要到达两个顶点之一外,接下来的步骤都要避免到达顶点(体现到倒水的过程里就是要避免两个桶到时到达满或空的状态),这样,每一步的选择就唯一了,从图上直观的看就类似于一个“反弹”的过程,最终到达目标,以下是一组解


这样,对于满足“两桶容量之和=第三桶容量”的所有三桶水问题,不需要通过程序,只需要有一张纸和一支笔,就可以用这种方法来求解!

根据上面从图中分析得到的规则,写出下面的求解程序


public class Solver {
//每个桶当前水量
int[] cup = new int[3];
//每个桶 容量
int[] max = new int[3];
//上一步操作中没有动过的桶
int lastfree;
//上一步操作中满或者空的桶
int lastreach;
public Solver() {
max[0] = 13;
max[1] = 17;
max[2] = 30;

cup[2] = 30;
}

public void next() {
int from;
int to;
//如果上一步有桶空了,则应该从上一步没动过的桶倒到该桶
if (cup[lastreach] == 0) {
to = lastreach;
from = lastfree;
}else if(cup[lastreach] == max[lastreach]) {
//反之如果有桶满了,则应该从该桶倒到没动过的桶
from = lastreach;
to = lastfree;
}else {
//根据论证,每一步必有一个桶空或者满
throw new IllegalStateException("Something must be wrong");
}
if(cup[from] > max[to] - cup[to]) {
cup[from] -= max[to] - cup[to];
cup[to] = max[to];
lastreach = to;
}else {
cup[to] += cup[from];
cup[from] = 0;
lastreach = from;
}
lastfree = 3 - (from + to);
}
}
public class Solver {
//每个桶当前水量
int[] cup = new int[3];
//每个桶 容量
int[] max = new int[3];
//上一步操作中没有动过的桶
int lastfree;
//上一步操作中满或者空的桶
int lastreach;
public Solver() {
max[0] = 13;
max[1] = 17;
max[2] = 30;

cup[2] = 30;
}

public void next() {
int from;
int to;
//如果上一步有桶空了,则应该从上一步没动过的桶倒到该桶
if (cup[lastreach] == 0) {
to = lastreach;
from = lastfree;
}else if(cup[lastreach] == max[lastreach]) {
//反之如果有桶满了,则应该从该桶倒到没动过的桶
from = lastreach;
to = lastfree;
}else {
//根据论证,每一步必有一个桶空或者满
throw new IllegalStateException("Something must be wrong");
}
if(cup[from] > max[to] - cup[to]) {
cup[from] -= max[to] - cup[to];
cup[to] = max[to];
lastreach = to;
}else {
cup[to] += cup[from];
cup[from] = 0;
lastreach = from;
}
lastfree = 3 - (from + to);
}
}


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/CoderPlusPlus/archive/2010/08/06/5794336.aspx
...全文
299 11 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
qiaopeizhan 2010-08-12
  • 打赏
  • 举报
回复
做个标记!!!!
super_stan 2010-08-10
  • 打赏
  • 举报
回复
特别好玩的东西啊,攒一个
Qin_Tianxiang 2010-08-10
  • 打赏
  • 举报
回复
前两天看到一个帖子
三个水桶,容量为13升,容量为17升,容量为30升,30升桶装满水,把水平均求分成两个15升的算法
danny15 2010-08-10
  • 打赏
  • 举报
回复
不知道楼主要解决什么问题。
danny15 2010-08-10
  • 打赏
  • 举报
回复
智商太低看不懂啊,接分
zxjdai 2010-08-09
  • 打赏
  • 举报
回复
改天再看,今天头晕
a88352871 2010-08-09
  • 打赏
  • 举报
回复
菜鸟学习中
Kwok 2010-08-07
  • 打赏
  • 举报
回复
顶一下,那个10分就下线!
bawgiitx 2010-08-07
  • 打赏
  • 举报
回复
学习了~~
huguang 2010-08-07
  • 打赏
  • 举报
回复
占座学习
染指黄昏 2010-08-07
  • 打赏
  • 举报
回复
学习

51,398

社区成员

发帖
与我相关
我的任务
社区描述
Java相关技术讨论
javaspring bootspring cloud 技术论坛(原bbs)
社区管理员
  • Java相关社区
  • 小虚竹
  • 谙忆
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧