经典证明:几个利用概率法进行证明的例子

    概率论并不仅仅是用来算算概率的。有些时候,概率论远比我们想象中的更强大。

    考虑这样一个问题。考虑集合X上的一个集合族,集合族中的所有集合大小均为d。我们说这个集合族是可以二染色的,如果对X的元素进行适当的红蓝二着色之后,每个集合里面都包含了两种颜色的元素。例如,当d=3时,{1,2,3}, {1,2,4}, {1,3,4}, {2,3,5}就是可二染色的,把1、2染成红色,把3、4、5染成蓝色,则每个集合里都含有两种颜色。是否存在d=3的不可二染色集族呢?这样的集族当然是存在的,例如取集合{1,2,3,4,5}的全部C(5,3)个元素个数为3的子集,则无论如何染色,总会有一个集合里面的元素全是一种颜色。上述推理立即告诉我们,对于一个给定的d,一定存在一个集合个数为C(2d-1, d)的不可二染色集族。这个数目还能再少吗?我们想知道,不可二染色集族中的集合个数最少可以少到什么地步。一个极其简单的证明给出了一个下界:集族的大小一定大于2^(d-1)。当d=3时,你一辈子也不能构造一个不可二染色集族,里面只含4个集合。
    为了证明这一点,不妨对X中的所有元素进行随机着色,每个元素取成红色和蓝色的概率均等。那么,一个元素个数为d的集合中,所有元素均为一种颜色的概率就应该是1/2^(d-1)。如果集族内的集合个数只有不到2^(d-1)个,那么即使“集合中是否只有一种颜色”是互相独立的,这些事件的并(至少有一个集合内只有一种颜色)的概率也不超过2^(d-1) * 1/2^(d-1) = 1,何况这些事件还不是独立的,因此存在单色集合的概率必然小于1。这个概率值小于1说明什么?这说明,“至少有一个单色集合”并不是必然事件,一定有一种染色方案使得每个元素里都含两种颜色,换句话说该集族可以被二染色。

Read more…

趣题:在双向有序链表中查找指定的数

    大家都知道,在一个有序数组里查找指定的数可以做到O(logn)的复杂度。但是大家想过没,在一个有序链表中又怎么样呢?让我们假设有这样一个链表,每个元素都严格小于它的后继元素。每个元素都能访问到自己的前驱元素和后继元素(如果有的话)。另外,我们知道每个元素在内存中的地址,因此可以进行随机存取。或者可以说,这个有序链表中的所有元素都是储存在一个数组中的,但数组本身并不有序。
    现在,我们需要在这个链表中寻找一个指定的数x。你能否设计出一个平均复杂度低于O(n)的算法来?

Read more…

《从一到无穷大》选谈:思维的尺度

    这个月月初就开始看《从一到无穷大》,花了接近两个星期才看完。这确实是一本让人放不下手的好书。考虑到我的阅读速度,一个多星期一本书已经近乎神速了。在这本书里我经常会看到一些有趣的数学知识,前段时间我还写过书里提到的一个有趣的东西——环面上的染色问题反而比平面上的“四色问题”更加简单。这种例子并不罕见,很多时候一些扩展版的问题反而比原问题更加简单。在第八章,我看到了另一个好玩的东西:随机游走(random walk)问题。
    随机游走问题是说,假如你每次随机选择一个方向迈出一个单位的长度,那么n次行动之后你离原点平均有多远(即离原点距离的期望值)。有趣的是,这个问题的二维情况反而比一维情况更加简单,关键就是一维情况下的绝对值符号无法打开来。先拿一维情况来说,多数人第一反应肯定是,平均距离应该是0,因为向左走和向右走的几率是一样的。确实,原点两边的情况是对称的,最终坐标的平均值应该是0才对;但我们这里考虑的是距离,它需要加上一个绝对值的符号,期望显然是一个比0大的数。如果我们做p次实验,那么我们要求的平均距离D就应该是

  

    其中d的值随机取1或者-1。这里的绝对值符号是一个打不破的坚冰,它让处于不同绝对值符号内的d值无法互相抵消。但是,当同样的问题扩展到二维时,情况有了很大的改变。我们把每一步的路径投射到X轴和Y轴上,利用勾股定理我们可以求出离原点的距离的平方R^2的值:

  

    一旦把平方展开后,有趣的事情出现了:这些X值和Y值都是有正有负均匀分布的,因此当实验次数p充分大时,除了那几个平方项以外,其它的都抵消了。最后呢,式子就变成了

  

    于是呢,就有平均距离R=sqrt(n) (准确的说是均方根距离)。我们得出,在二维平面内随机选择方向走一个单位的长度,则n步之后离出发点的平均距离为根号n。这是一个很美妙的结论。

Read more…

随机洗牌:哪一种算法是正确的?

    记得当年搞NOIp时,我犯过一个相当严重的错误:错误地把Floyd算法的i, j, k三层循环的位置顺序搞颠倒了。直到准备省选时我才突然意识到,Floyd算法应该最先枚举用于松驰操作的那个“中间变量”k,表示只经过从1到k的顶点的最短路;而我却一直习惯性地以为i, j, k应该顺次枚举。令人惊讶的是,这个错误跟了我那么久我居然从来都没有注意到过。后来,我发现有我这种经历的人不止一个。惯性思维很可能会让你接受一些明显错误的算法,并且让你用得坦坦荡荡,一辈子也发觉不了。
    假使你需要把一个数组随机打乱顺序进行重排。你需要保证重排后的结果是概率均等、完全随机的。下面两种算法哪一种是正确的?其中,random(a,b)函数用于返回一个从a到b(包括a和b)的随机整数。

1. for i:=1 to n do swap(a[i], a[random(1,n)]);
2. for i:=1 to n do swap(a[i], a[random(i,n)]);

Read more…

趣题:尽可能用奇数次猜测完成猜数游戏

    现在,我在心里想一个不超过n的正整数t。你的任务是尽可能用奇数次猜测猜中这个数(你知道n是多少)。每次猜测后,我都会告诉你你所做的猜测是大了还是小了。你不能猜测已经被排除了的数(来消耗猜测次数),你的每次猜测都必须符合我原来给出的回答。你觉得,你获胜(奇数次猜中)的几率有多大?

 
    动态规划的几个类似的经典模型启发了我们:设a[m]表示采取最优策略后在m个数里猜奇数次猜中的概率,b[m]表示如果题目要求我们猜偶数次,那最优策略下有m个数时获胜的概率是多少。考虑现在我有m个数可以猜,我想在奇数次内猜中。现在我猜的是数字i。狗屎运最好时,我一次猜中直接就赢了,它的概率是1/m;有(i-1)/m的情况下我会得到“大了”的提示,这样的话我需要用偶数次猜测去猜前面那i-1个数;剩余的那(m-i)/m的情况中,我需要用偶数次猜测去猜m-i个数。因此,a[m] = Max {1/m + (i-1)/m * b[i-1] + (m-i)/m * b[m-i], 1≤i≤m} 。类似地,我们也可以得出b[m]的递推公式:b[m] = Max {(i-1)/m * a[i-1] + (m-i)/m * a[m-i], 1≤i≤m} 。
    学习使用Mathematica确实是一件好事,你可以用Mathematica非常方便地描述出我们上面的两个递推公式,不需要自己去写那些冗长的程序了。
a[m_] := Max[Table[1/m + (i-1)/m * b[i-1] + (m-i)/m * b[m-i], {i, m}]]; a[0] := 0;
b[m_] := Max[Table[(i-1)/m * a[i-1] + (m-i)/m * a[m-i], {i, m}]]; b[0] := 0;

Read more…