代码改变世界

一个flash游戏引发的思考【算法】

2007-01-12 20:08  老博客哈  阅读(1164)  评论(3编辑  收藏  举报
很久很久之前(汗...),kaikai给了我一个flash游戏玩(放在这下载了),大概的意思是
5个人在分别再17楼,26楼,20楼, 19楼,31楼等着下电梯,他们下电梯的位置都是在21~25层楼之间(包含)但是规定只能两个人同时上或者同时下电梯,并且上电梯只能一次上升8个单位,下降只能下降13个单位,其中楼层高度为49层楼,现在让你通过操作这么5个人,使得他们能够顺利的到达指定楼层。
当时kaikai貌似是用手算然后得出结果8的,我当时第一印象是写段程序解决,后来时间不够就丢在一边了,最近整理硬盘的时候无意中发现了,就着手把这个做完了。
把这个游戏中中的模型抽象出来为:
有5个数字, 17, 26, 20, 19, 31, 现在要求同时操作其中的两个数字,使他们同时+8,或者同时-13,使得所有的数字最终都处在[21,25]之间,注意中间结果必须在[1,49]之间,求最少需要操作的次数?

下面的分析是结合Felica, 怎么我也,kaikai,gardon, dj都诸位大牛基础上分析的。
首先分析一下可行性,由于8和13互质,根据
(裴蜀恒等式)如果两个数a,b的最大公约数是d,那么存在两个整数x与y,使得等式ax+by=d成立。
则存在8x + 13y = 1,当然也就存在8x - 13y = [1, 49], 因此处在每一层上的人都有可能到达目标层。
那么怎么处理这个问题呢,一个比较直接的想法:是将这5个人所处的位置作为一个状态,然后进行bfs,将每一个状态保存,最终得到结果。当我写完程序之后发现这个无比缓慢,因为一个状态会派生出C(5, 2) * 2=20个状态,结果会很慢,实验结果等待了5分钟也没有结果。那么该怎么处理呢?剪枝,对了!
dj师兄告诉我可以用“胡乱剪”,就是将一些看似不可能的状态直接给去掉,不用扩展,这样带来的后果是结果可能不正确,牺牲正确性带来效率,有点“Monte Carlo”算法味道。Felica推荐了hash。这里我用了后者,由于所处的层数只是在1~49之间,而49 < 63 = 2^6 - 1,因此将每一个状态里面5个人的层数分配给6位组合成一个int的数字就能避免了hash的冲突,也就带来了效率。
下面是实现的C++代码:

下面是运行结果:
1个人升1次,降0次  最终到达25层
2个人升3次,降2次  最终到达24层
3个人升2次,降1次  最终到达23层
4个人升2次,降1次  最终到达22层
5个人升2次,降2次  最终到达21层
8
嗯, 现在看来工作已经差不多了,可是您可能会发现上面的算法是多么的低效,保留了很多无用的状态,仅仅是利用这些状态去扩展.下面我们变化思路,先求得各个人到达目标层的单个状态(SingleState),然后再不断的组合次优解,进而判断当前的整合状态是否能够满足"抵消"(这里的抵消指的是同时以不同的两个电梯升或者降使得最终没有剩余.)这个数学模型可以理解为有若干不同容量的箱子,里面装满了小球,现在有两个人,每次从不同的盒子里面拿球,判断最终是否有小球剩余.这个根据Gardon大牛的指示,可以每次取最大和次大的中的每一个球,
得到的结论是当总的球数为偶数并且最大球的数目<= 总数的1/2就可以了.(具体证明还不会...汗).
我下面就是使用了5个队列不断的去求得每个电梯的最优解,然后进行组合,这样就相比上面高效多了,
下面是具体的实现代码:
结果为:
第1个人升1次,降0次  最终到达25层
第2个人升3次,降2次  最终到达24层
第3个人升2次,降1次  最终到达23层
第4个人升2次,降1次  最终到达22层
第5个人升2次,降2次  最终到达21层

其实不在于先升还是先降,适当组合会产生很多不同的结果,只要上升数量和下降数量不变即可.

最后去试试这个游戏吧,会得到一个意想不到的惊喜哦~