之前的数学老师遇到我不会做的古典概率题,有时候会告诉我:那你就去穷举啊实在不会做就试着穷举看看,如果情况不多就还能做出来。不过穷举也和思路有关,因为穷举的时候首先要知道,列出的这些情况是否是等概率的情况。比如下面这道题,是一个朋友问我的;这里有一个正确答案和一个错误答案。

题目

一个人把六根草紧握在手中,仅露出它们的头和尾。然后随机地把六个头两两相接,六个尾也两两相接。求放开手后六根草恰巧连成一个环的概率。

六根草接成一个大环就是说,不出现两根草连在一起的小环和四根草连在一起的小环。由于每根草的头尾都和其他草相接,所以不会不连成环。

由于四根草连成小环的时候另外两根也连成小环,所以可以说:六根草连成一个大环的情况就是不出现两根草头尾均相接的情况。

我的思路

考虑六根草的头已经两两连接在一起成为三组,此时只需要把尾接好就可以了。这个时候,要保证六根草连成一个大环,就不能把同组的尾连在一起。

  • 首先选一根草。由于这时六根草是等价的,所以选哪根都一样;之后,在剩下的五根草里选一根,和这根草尾部相连。有两种情况:
    1. 选到的是和这根草不同组的草。不同组的草有四根,故这种情况出现的概率占 ;将上面两根草相接后,产生了一组四根草和一组两根草。在剩下的四根草中再选一根草。有两种情况:
      1. 选到的是四根草一组中的草。这组草有两个头,所以概率占 ;在剩下的三根草中选取一根和这根草相连,也有两种情况:
        1. 和两根草组的草相连,满足了题目条件;概率占
        2. 和本组的草相连,不能满足题目条件;概率占
      2. 选到的是两根草一组中的草。这组草有两个头,所以概率占 ;在剩下的草中选取一根和这根草相连,也有两种情况:
        1. 和四根草组的草相连,满足了题目条件;概率占
        2. 和本组的草相连,不能满足题目条件;概率占
    2. 选到的是和这根草同组的草。这种情况出现的概率占 ,此时不能满足题目条件。
  • 最终满足条件的分支的总概率为:

另一个思路

在和那位朋友对了答案之后,我确定了正确结果就是 .不过那位朋友告诉我,他的一位朋友认为答案是 ,理由是这样的:

他用了画图的方法来分析这些情况。画图能得到的结果有三种:

  1. 六根草连成一个大环,即满足题目要求的情况;
  2. 四根草连成一个环,两根草连成一个环;
  3. 三组两根草连成三个小环。

所以满足条件的概率为

在我和我的朋友质疑的时候他给出理由:从第一组选一根草开始连,只要连的是同一组的两根,就是等价的结果。而我们继续质疑这样等价会造成结果不等可能后,他说:那你就去穷举啊他所穷举出的结果只有上面的那三种可能。

我认为这是不适合的穷举造成的错误;他使用画图来穷尽所有的结果,但是一方面最终视觉上的结果不一定是等可能的,另一方面画图为这题的概率选择增添了额外的信息量:在连这几根草的尾部时,这个人并不知道头部是如何连接的——也就是说他并不知道哪几根草在同一组;但这一信息在图上反映了出来。于是我决定让他看看什么是真正的穷举把这道题的结果用 Python 穷举一下,其中包括模拟错误的思路,和正确的思路。

代码

我用一个list来表示一个结果,用相同的数字表示头在同一组的草;在list中相邻表示两根草尾相连。

result = [1, 2, 1, 3, 2, 3]
# 第一组的草a连第二组的草a,第一组的草b连第三组的草a,第二组的草b连第三组的草b
# 这一结果满足题目要求

result = [1, 2, 1, 2, 3, 3]
# 第一组的草a连第二组的草a,第一组的草b连第二组的草b,第三组的草a连第三组的草b
# 这一结果不满足要求

代码的完整输出偏长,这里不展示;只展示最后的结果。

错误的结果

这一段程序通过用 listindex 来穷举,得到的是错误的结果。

print('wrong answer:')
total = 0
hit = 0
for i in range(3):
    choose = [1, 2, 3]
    result = []
    result.append(1)
    result.append(choose.pop(i))
    for j in range(2):
        result.append(2)
        result.append(choose[j])
        result.append(3)
        result.append(choose[1 - j])
        print(result, end=' ')
        if result[0] == result[1] or result[2] == result[3] or result[4] == result[5]:
            total += 1
            print('no')
        else:
            total += 1
            hit += 1
            print('yes')
        result.pop()
        result.pop()
        result.pop()
        result.pop()
print('prob = ' + str(hit) + '/' + str(total))

运行后,最后一行的输出为:

prob = 2/6

正确的结果

这一段程序不考虑组号,通过对六根草进行全排列进行穷举,得到的结果是正确的。

print('right answer:')
total = 0
hit = 0
for i in range(6):
    choose = [1, 1, 2, 2, 3, 3]
    result = []
    result.append(choose.pop(i))
    for j in range(5):
        choose5 = choose.copy()
        result.append(choose5.pop(j))
        for k in range(4):
            choose4 = choose5.copy()
            result.append(choose4.pop(k))
            for l in range(3):
                choose3 = choose4.copy()
                result.append(choose3.pop(l))
                for m in range(2):
                    result.append(choose3[m])
                    result.append(choose3[1 - m])
                    print(result, end=' ')
                    if result[0] == result[1] or result[2] == result[3] or result[4] == result[5]:
                        total += 1
                        print('no')
                    else:
                        total += 1
                        hit += 1
                        print('yes')
                    result.pop()
                    result.pop()
                result.pop()
            result.pop()
        result.pop()
print('prob = ' + str(hit) + '/' + str(total))

运行后,最后一行的输出为:

prob = 384/720

即正确结果

后记

写完才想起来 Python 是有直接列举排列组合的函数的,就是 itertool.combinations()itertool.permutations();而且我匆忙之中代码写得很烂。反正都穷举了,做对就行了