和吴昊一起玩推理 Round 2 —— 蚂蚁爬杆问题

Problem:有一根27厘米长的细木杆,在第3厘米,7厘米,11厘米,17厘米,23厘米,这五个位置上各有一只蚂 蚁,木杆很细,不能同时通过两只蚂蚁,开始时,蚂蚁的头朝向左还是右是任意的,他们只会朝前走或掉头,但不会后退,当两只蚂蚁相遇后,蚂蚁会同时掉头朝反 方向走,假设蚂蚁们每秒钟可以走1厘米的距离。求所有蚂蚁都离开木杆的最小时间和最大时间。

 

一刻推理:首 先可以想到的一个方法,就是模拟算法,没有错,由于这些蚂蚁都有其相似的地方,故可以归为一个类,而有多少个蚂蚁,就有多少个对象。问题是,这样做,确实 是可以详细描述出这些蚂蚁活动的全过程,但是,问题要去的是,我们只需要求出所有蚂蚁离开木杆的最小时间(MinTime)和最大时间(MaxTime) 即可,我们当然可以尝试左右情况,但是,问题是,假设所有的蚂蚁的数量为n,则需要尝试的总次数为O(2^n),这样是NP难的,有木有更神奇的方法?

二刻推理:通过联想,很容易想到那位老外给苏步青的一个难题,也就是说,一只狗在两个人之间来回以相同的速度跑,我们是否需要详细地模拟全过程?当极限的概念未知的情况下,此问题甚至是无解的!想到这里,我们应该可以得到,必须要跳开这种纯粹模拟的思维,寻找更玄妙的途径。

三刻推理:如 果题目条件没有那么“强”,比如,杆子没有细到不能同时通过两只蚂蚁,如果可以通过两只蚂蚁,甚至是更多的蚂蚁,无非就是一个纯粹的O(n)可以解决的问 题了,但是,有些问题,适当地放宽条件,问题还是成立的!本题在比较弱的条件下可以成立,那么,能不能在加强版的条件下仍然成立呢?

四刻推理:由于蚂蚁都是完全相同的,我们可以悟到一点,也就是说,两个蚂蚁如果碰撞的话,其中一只蚂蚁可以“穿过”另外一只蚂蚁的身体,想到了这一点,问题迎刃而解了。NP难问题化为了一个时间复杂度为O(n)的问题!这就是推理带来的效果。


 1 /*
 2 #include "stdafx.h"
 3 #include <iostream>
 4 using namespace std;
 5 
 6 */
 7 根据题意:两只蚂蚁相遇后,各自掉头朝相反方向走。但是,我们可以“看着”
 8 两只蚂蚁相遇后,擦肩而过,即,认为蚂蚁是相互独立的,是否碰头没什么关
 9 系。
10 所有蚂蚁都离开木杆的最小时间为
11  max(min(3,27-3),min(7,27-7), min(11,27-11), min(17,27-17),min(23,27-23))=11
12 所有蚂蚁都离开木杆的最大时间为
13  max(max(3,27-3),max(7,27-7), max(11,27-11), max(17,27-17),max(23,27-23))=24
14 */
15 
16 void antCrawlTime(double length,       //length of stick
17                                 double *xPos,       //position of an ant,<=length
18                                 int antNum,              //number of ant
19                                 double speed,      //speed of crawl
20                                 double &min,        //min of leave time
21                                 double &max)       //max of leave time
22 {
23     double totalTime=length/speed;
24     min=0;
25     max=0;
26     for(int i=0;i<antNum;i++)
27     {
28         double tmpMin=0;
29         double tmpMax=0;
30         if(xPos[i]<length/2)
31         {
32             tmpMax=(length - xPos[i])/speed;
33         }
34         else
35         {
36             tmpMax=xPos[i]/speed;
37         }
38         tmpMin=totalTime-tmpMax;
39 
40         if(max<tmpMax)
41         {
42             max=tmpMax;
43         }
44         if(min<tmpMin)
45         {
46             min=tmpMin;
47         }
48     }
49 }
50 
51 int _tmain(int argc, _TCHAR* argv[])
52 {
53     double xPos[]={3,7,11,17,23};
54     double min,max;
55     antCrawlTime(27,xPos,5,1,min,max);
56     cout<<"min:"<<min<<"***max:"<<max<<endl;
57     return 0;
58 }

拓展推理:博客上有网友更进一步地提出了如下五个问题,这里给出博客上的详细信息以及解答,其中,[Math Processing Error]为一个数字,可能是相关的编码错误吧!

  1. 第[Math Processing Error]只蚂蚁什么时候走出木杆?
  2. 所有蚂蚁从一开始到全部离开木杆共碰撞了多少次?
  3. 第[Math Processing Error]次碰撞发生在哪个时刻?哪个位置?哪两个蚂蚁之间?
  4. 哪只蚂蚁的碰撞次数最多?
  5. 如果不是一根木杆而是一个铁圈,经过一段时间后所有蚂蚁都会回到的状态吗?这个时间的上界是多少?

扩展1的解答

现 在来解决扩展1。这个解答甚是精妙,通俗点来说,我们假设每只蚂蚁都背着一袋粮食,任意两只蚂蚁碰头时交换各自的粮食然后调头。这种情况下,每次 有一只蚂蚁离开木杆都意味着一袋粮食离开木杆(虽然可能已经不是它刚开始时背的那一袋了)。于是,我们可以求出每袋粮食离开木杆的时间(因为粮食是不会调 头的)。又由于每袋粮食离开木杆的时间都对应某只蚂蚁离开木杆的时间,这是一种一一映射关系。现在我们要找到对应于第[Math Processing Error]只蚂蚁的那个映射。在此之前需要证明一个命题:

若一开始时有[Math Processing Error]只蚂蚁向左走,[Math Processing Error]只蚂蚁向右走,则最终会有[Math Processing Error]只蚂蚁从木杆左边落下,[Math Processing Error]只蚂蚁从木杆右边落下。

这个命题很容易证明:每次碰撞 均不改变向左和向右的蚂蚁数量。于是,由于每次碰撞蚂蚁都会调头而不是穿过,最后必定是前[Math Processing Error]只蚂蚁从左边落下,后[Math Processing Error]只蚂蚁从木杆右边落下。由于我们知道每袋粮食是从哪边落下的,故左边落下的[Math Processing Error]袋粮食的离开木杆的时间就对应于前[Math Processing Error]只蚂蚁离开木杆的时间,右边的类似。因此,我们只需判断[Math Processing Error]和[Math Processing Error]的关系,便知道第[Math Processing Error]只蚂蚁是从左边还是右边落下。不妨假设是从左边落下,因此该蚂蚁落下的时间就等于从左边落下的第[Math Processing Error]袋粮食的落下时间。时间复杂度[Math Processing Error],一遍扫描搞定。

扩展2的解答

对 于扩展2,我们只需求得每个蚂蚁的碰撞次数,然后累加即可。在这里我们换一种思路,把碰撞调头看成不调头而继续向前(穿过),则容易看出原问题 (碰撞次数)就变为求穿过的次数(因为速度大小不变,原来的每次碰撞都对应于现在的一次穿过)。则对于每只向左的蚂蚁,它只会“穿过”那些在它左边的向右 走的蚂蚁。因此,每只蚂蚁“穿过”的蚂蚁次数等于刚开始时在它前进方向上与它前进方向相反的蚂蚁个数。时间复杂度也是[Math Processing Error]。改为用粮食的观点来理解也是可以的。

扩展3的解答

第3个扩展问 题有点复杂。首先我们假设[Math Processing Error]为0.5个单位长度每秒,每个蚂蚁刚开始时都处于整点处,这样每次碰撞都发生在整秒处。这个假设有个好处,就是我们可以二分第[Math Processing Error]次 碰撞的时刻。如果碰撞时刻是浮点数,这个二分有可能永远不会终止。我们还是看成每个蚂蚁驮着一袋粮食,那么每袋粮食易主(即从一个蚂蚁身上交换到另一个蚂 蚁身上)时,就发生了一次碰撞。由于粮食的方向是固定不变的,我们可以很容易求出每一袋粮食在它的“前进”方向上的所有易主时刻(它易主的次数等于扩展2 中的“穿过”次数)。这样,我们的问题就等价于:

找到最小的时间[Math Processing Error],使得易主时刻小于或等于[Math Processing Error]的易主次数大于或等于[Math Processing Error]。

由 于现在所有碰撞(易主)的时刻都是整点,故我们可以二分[Math Processing Error],然后用线性复杂度找出易主时刻小于或等于[Math Processing Error]的易主次数。整个复杂度为[Math Processing Error],其中maxt和mint分别表示第一次和最后一次碰撞的时刻,均可在[Math Processing Error]时间内求出。

在上一段中,要想使用线性时间复杂度求出易主时刻小于或等于[Math Processing Error]的易主次数还需要一点技巧。可以这样:用一个数组[Math Processing Error]表示第[Math Processing Error]个向右走的蚂蚁的初始位置,当扫描到第[Math Processing Error]个向左走的蚂蚁时,假设得到的中值点为[Math Processing Error](即[Math Processing Error]到第[Math Processing Error]个位置上对应的粮食和该袋粮食的易主时刻均大于[Math Processing Error])。由于该袋粮食向左易主的时刻是递增的,而下一个向左走的蚂蚁的初始位置又大于当前(第[Math Processing Error]个向左走的)蚂蚁,故对于下一个蚂蚁ant来说,[Math Processing Error]到第[Math Processing Error]个位置上对应的粮食和ant所驮粮食的易主时刻也一定大于[Math Processing Error]。即中值点的位置是单调的。因此可以在均摊[Math Processing Error]的时间内算出所求个数。

求出时刻的 同时我们也求出了位置,故第二小问也解决。接下来要求哪两个蚂蚁发生了这次碰撞(如果同时存在多个碰撞求出任意一个即可)。其实,我们只需要求出每袋粮食 在[Math Processing Error]时刻的位置即可。因为每袋粮食必然对应于一个驮着它的蚂蚁,故我们只需对这些粮食的位置排序,找出位置相同的粮食以及其下标(即从左到右第几 个),也就找出了那对蚂蚁了。

扩展4的解答

对于第4个扩展,只要求出每只蚂蚁的 碰撞次数即可。这也解决了扩展2的解答中原始思路。首先由扩展1的解答我们可以知道每只蚂蚁最终是往左还是右掉下去,然后假设第[Math Processing Error]只蚂蚁最终往左掉下,而开始时刻其左边有[Math Processing Error]只向右走的蚂蚁,则它至少要朝左边碰撞[Math Processing Error]次才能把左边的蚂蚁全撞成向左的状态。倘若它一开始就是向左的,则共要碰撞[Math Processing Error]次,否则为[Math Processing Error]次。这样利用一个数组和几个计数器仍能在[Math Processing Error]时间内求出每个蚂蚁的碰撞次数,取最大那个即可。

扩展5的解答

这个 问题看起来挺复杂,其实很简单。假设环长为[Math Processing Error],则一个蚂蚁走完一圈需要[Math Processing Error]的时间。首先,还是像上面的讨论那样假设每个蚂蚁都驮着一袋粮食。那么,经过[Math Processing Error]时间后所有粮食都回到了原来的位置。由于每袋粮食都对应一个蚂蚁,而蚂蚁每次碰撞都会调头,因此蚂蚁的相对位置是不变的,这就说明经过 [Math Processing Error]时间后蚂蚁循环移动了。假设移动了[Math Processing Error]个位置,即每个蚂蚁都到达它往右第[Math Processing Error]个蚂蚁的初始位置,那么,类似地,再经过[Math Processing Error]时间,当前状态仍会循环移动[Math Processing Error]个位置。容易看出这是一个最小公倍数问题:循环移动多少个[Math Processing Error]次之后每个蚂蚁回到自己位置?答案为[Math Processing Error],于是最多经过[Math Processing Error]时间,每个蚂蚁都至少回到原地一次。

除了以上几个扩展,还有一些个人认为比较变态的扩展,有的没空仔细想,有的暂时没想到解法,也列出如下,欢迎拍砖:

  1. 如果每只蚂蚁的速度不一样(这就有可能由于追赶而产生碰撞,此时根据动量守恒定律:(,速度互换),上述扩展问题的答案是什么呢?
  2. 如果蚂蚁在一个平面上运动,同样也是碰头后原路返回(注意这不等同于两只蚂蚁交换继续前进),问是否所有蚂蚁都能最终离开平面?
  3. 在上述情况下,如果最终能离开平面,离开平面需要多长时间?
  4. 在上述情况下,回答关于一维的前文讨论的每个问题。

另外,赵牛同学又提出了一些更bt的扩展,如下:

  1. 假设每个蚂蚁都有重量,两只蚂蚁碰撞时轻的那个有一定几率从旁边被撞下去:(,那又该怎样?
  2. 假设不是被撞下去而是有一定几率被撞晕而停滞几秒,那又该怎样?
  3. 各种无语………

 

posted on 2013-02-27 22:16  吴昊系列  阅读(475)  评论(0编辑  收藏  举报

导航