一个有趣的问题:带锁的门
2012-8-29 18:08
===================
问题是这样的:
* 一条走廊上有n 个带锁的门编号1、2、3、……、n依次排列,门的初始状态都是关着的。
* 每一次都从1号门开始依次经过所有门,在第k次经过所有门时改变编号是k的整数倍的门的状态(即若门关着,就打开它,门开着,就关闭它),例如进行第一次时因为所有数都是1的倍数,所以会打开所有的门(注意所有门初始状态是关着的),进行第二次时改变所有编号是2的倍数的门的状态,进行第三次时改变所有编号是3的倍数的状态……,我们总共经过n次后停止。
问题:在最后一次经过所有门后,哪些门是打开的?
先计算问题答案:
对于这个问题,我先写了一个程序来看了看到底最后哪些门是开着的(代码及其实现细节在文章最后给出),运行程序后发现开着的门的编号都是完全平方数(即该数等于某正整数的平方),而其他门都是关着的。接下来分析为什么平方数是此问题的答案。
分析问题:
*基础过程分析:第k次经过这些门后,我们都会改变编号是k的倍数的门的状态,于是容易知道第k次过程结束后,编号小于等于k的门的状态就已经不会再变了,因为下一次就是第k+1次了,小于k+1的数不可能是k+1的倍数。
*进阶分析:有了上面的基础分析,我们可知对于k号门来说,假设k的所有约数为a,b,c,那么只有在第a和b和c次经过所有门时才会改变k号门的状态,其他次数经过k号门时,它的状态都不会变,现在我们可以把k号门的最终状态描述为一个数学问题,即k有多少个约数,若k有奇数个约数,则我们会改变k号门的状态奇数次,易知k号门最终是开着的,若k有偶数个约数,则我们会改变k号门的状态偶数次,同样易知k号门最终是关着的。
然后我们的证明就可以直逼最终答案了,我们只需证明对于所有正整数1、2、3、……、n,只有完全平方数的约数个数是奇数,其他所有数的约数个数均为偶数。
这种问题显然会涉及算数基本定理:任何一个比1大的正整数都能写成若干(>0)个质数的乘积的形式,且对于每一个被分解的数来说这种形式是唯一的,我们在写的时候会将乘积中相同的质数合并成一个质数的幂的形式,例如12 = 2^2*3^1;100 = 2^2*5^2。
故对于任意大于1的整数n,有n = P1^k1 * P2^k2 * P3^k3 * …… * Pi^ki;即我们把n用算数基本定理分解成了若干质数的乘积的形式,P表示质数,k表示P的指数。
容易证明n的约数个数等于(k1+1)*(k2+1)*(k3+1)*……(ki+1),到这里已经看到答案了,要想知道最终哪些门是开着的,只需找到约数个数为奇数的编号就可以了,因此对于乘积(k1+1)*(k2+1)*(k3+1)*……*(ki+1) 来说,每一项(k+1) 都必须为奇数才能保证乘积结果为奇数,换句话说,只有每一个k都是偶数才能保证结果为奇数,下面我们证明只有完全平方数才具有这样的性质。
分两方面来证明:第一方面,若每一个k都是偶数,则n = P1^k1 * P2^k2 * P3^k3 * …… * Pi^ki = (P1^(k1/2)* P2^(k2/2) * P3^(k3/2) * …… * Pi^(ki/2))^2,即改变算数基本定理分解后的形式,给每一个偶数k除以2,再整体平方,将n化成了一个完全平方数的形式,到此我们证明了若门是开着的,则其编号必为完全平方数。第二方面:对于所有的完全平方数,都可以写成两个相同正整数的乘积(这是完全平方数的定义),设完全平方数S = M^2,然后将这个M分解以后得到S = (P1^j1 * P2^j2 * P3^j3 * …… * Pi^ji)^2 = P1^2j1* P2^2j2 * P3^2j3 * …… * Pi^2ji,其形式等同于k为偶数时S = P1^k1 * P2^k2 * P3^k3 * …… * Pi^ki,即证明了所有完全平方数都有奇数个约数。
至此,我们终于证明了只有编号为完全平方数的门最终是开着的!
分析问题可以明显看出此问题的二进制模型——门的开和关,马上想到用bitset轻松建立多个门排列在一条走廊上的模型。特别注意bitset的最低位的位置,高位在前,即0位是最右边一位,因此最右边一位代表1号门。
验证此过程的C++代码
1 /** 2 * n个带锁的门编号1、2、3、……、n依次排列,初始状态是锁着的。 3 * 每一次都从1号门开始依次经过所有门,在第i次经过时改变i的整数倍号锁的状态(仅含开关两种状态) 4 * 以下程序验证上题会发现整个流程结束后,编号为平方数的锁是开着的,其他都是关着的 5 * 谜之音:哈?六万个门?…………开一遍就跪了………… 6 */ 7 #include <iostream> 8 #include <bitset> 9 using namespace std; 10 int main() { 11 //门的数目,定为60000 12 const int size = 60000; 13 //门数加1,用于修正循环体i的极限值 14 const int size_plus = size + 1; 15 ///使用bitset实现,位序列号代表门编号,该位是0代表锁是关闭的,1代表锁是打开的 16 ///特别注意bitset的最低位的位置,高位在前,即0位是最右边一位,因此最右边一位代表1号门 17 bitset<size> bitset_door; 18 19 ///循环实现,在第i次经过时改变i的整数倍号锁的状态,flip()取反即可 20 cout<<"calculating…………"<<endl; 21 for (int i = 1; i!=size_plus; ++i) { 22 for (int j = i; j!=size_plus; ++j) { 23 if (j%i==0) { 24 bitset_door.flip(j-1); 25 } 26 } 27 } 28 cout<<"complete !"<<endl; 29 ///输出结果,输出开着的门的编号 30 for (int i = 0; i!=size; ++i) { 31 if (bitset_door[i]==1) 32 cout<<i+1<<endl; 33 } 34 return 0; 35 }

浙公网安备 33010602011771号