1. 起因
看论文的时候,论文里简单提了一下。大概意思是,抛硬币1000次,至少连续10次正面朝上的概率比较大。我无聊就算了一下(后来就想拍死这个无聊的自己T^T)。
2. 问题陈述
一开始没什么思路,查了很多资料,答案五花八门。但是有一个观点让我很惊喜,他强调了一下问题本身。
这个问题到底什么意思?
- 角度1
抛硬币1000次,如果至少连续10次正面朝上,则称事件A发生。(连续11次、12次,或者有好多次“连续10次”都不管,只要至少有一次连续10次就看作事件A发生) - 角度2
抛硬币1000次,共有 2 1000 2^{1000} 21000种情况。可以看作一个基数为 2 1000 2^{1000} 21000的集合,记为 S S S。集合 S S S中的每一个元素都是长度为1000,仅包含字符0,1的字符串。集合 A ⊂ S A\subset S A⊂S,如果“1111111111”(10个1)是某一字符串的子串,那么该字符串在集合 A A A中。实际上集合 S S S就是样本空间,集合 A A A就是随机事件A。
我们想知道 ∣ A ∣ |A| ∣A∣。
3. 问题简化版本
好了,现在我们对问题已经比较清楚了。先看一个简化版本,这样有助于思考:连续抛10次硬币,至少连续两次正面朝上的概率是多少?
但好像不太好算欸:
- 思考1 分解事件A
事件 A A A=事件 B 2 B_2 B2(恰好连续两次正面朝上)+事件 B 3 B_3 B3(恰好连续三次正面朝上)+···+事件 B 10 B_{10} B10(恰好连续十次正面朝上)
- 事件 B 2 B_2 B2=事件 B 2 , 1 B_{2,1} B2,1(恰好一次连续两次正面朝上)+事件 B 2 , 2 B_{2,2} B2,2(恰好两次连续两次正面朝上)+事件 B 2 , 3 B_{2,3} B2,3(恰好三次连续两次正面朝上)(四次是不可能的)
- 事件 B 3 B_3 B3=事件 B 3 , 1 B_{3,1} B3,1(恰好一次连续三次正面朝上)+···
- ···
(我选择放弃)
- 思考2 反事件
P ( A ) = 1 − P ( A ‾ ) P(A)=1-P(\overline{A})\; P(A)=1−P(A) 但事件 A ‾ \overline{A} A也很难求
3.1 遇事不决 先Brutal一下
2 10 2^{10} 210好像也挺大的(但比 2 1000 2^{1000} 21000小得多),就直接暴力搜索一下吧:
# 暴力法直接搜索
# 投掷硬币m次 其中n次连续正面朝上的情况种数/概率
def brutal(m=10, n=2):
# 表示连续n次的子串,也就是关键字
s = '1'*n
count = 0
# 直接枚举样本空间
for i in range(2**m):
if s in bin(i)[2:]:
count += 1
# 返回符合条件的种数,以及概率P
return count, count/(2**m)
结果出来了,count=880, p=0.859375。这个结果可能还不够清晰,我们再看个更简单的例子:只抛四次硬币。
这时就比较直观了:(O表示正面,X表示反面)
- | - | - | - |
---|---|---|---|
XXXX | XOXX | OXXX | OOXX |
XXXO | XOXO | OXXO | OOXO |
XXOX | XOOX | OXOX | OOOX |
XXOO | XOOO | OXOO | OOOO |
发现brutal函数在这个输入下的输出是正确的
3.2 换个思路
我们先定义一个数列
A
n
k
A_n^k
Ank 表示投掷硬币n次,连续k次正面朝上的情况种数(数列下标还是喜欢n啊)
现在我们想求
A
10
2
A_{10}^2
A102:
要抛10次硬币,第一次要么正面,要么反面:(@表示任意)
O@@@@@@@@@ (情况1)
X@@@@@@@@@ (情况2)
- 对于(情况1),再考虑第二次抛硬币:
OO@@@@@@@@@ (情况1.1)
OX@@@@@@@@@ (情况1.2)- 对于(情况1.1),已经满足条件了,剩下8次就随便吧 2 8 2^8 28
- 对于(情况1.2),既然已经抛出反面了,又得重新开始了。 A 8 2 A_{8}^2 A82
- 对于(情况2),不就是投掷硬币9次的情况种数吗? A 9 2 A_{9}^2 A92
以上,我们可以得出
A
10
2
=
2
8
+
A
8
2
+
A
9
2
A_{10}^2 = 2^8+A_{8}^2+A_{9}^2
A102=28+A82+A92
推广一下:
A
n
2
=
2
n
−
2
+
A
n
−
1
2
+
A
n
−
2
2
A_{n}^2 = 2^{n-2}+A_{n-1}^2+A_{n-2}^2
An2=2n−2+An−12+An−22
再推广一下:
A
n
k
=
2
n
−
k
+
∑
i
=
1
k
A
n
−
i
k
(
1
)
A_{n}^k = 2^{n-k}+\sum_{i=1}^{k}A_{n-i}^k \quad(1)
Ank=2n−k+∑i=1kAn−ik(1)
用本节的推导方式可以验证 k = 10 k=10 k=10时,公式(1)是正确的
4. 用递推公式算算通项吧
4.1 线性代数解法
(这里巨坑,算了好久还算错了。最后用线性代数的解法才解出来)
递推公式:(隐含
k
=
2
\;k=2
k=2)
A
n
=
2
n
−
2
+
A
n
−
1
+
A
n
−
2
A_{n} = 2^{n-2}+A_{n-1}+A_{n-2}
An=2n−2+An−1+An−2配一下:
A
n
−
2
n
=
A
n
−
1
−
2
n
−
1
+
A
n
−
2
−
2
n
−
2
A_{n} -2^n= A_{n-1}-2^{n-1}+A_{n-2}-2^{n-2}
An−2n=An−1−2n−1+An−2−2n−2令
B
n
=
A
n
−
2
n
B_n=A_n-2^n\;
Bn=An−2n,则特征方程:
x
2
=
x
+
1
x
1
=
1
+
5
2
,
x
2
=
1
−
5
2
x^2=x+1\\ x_1=\frac{1+\sqrt{5}}{2},\;x_2=\frac{1-\sqrt{5}}{2}
x2=x+1x1=21+5,x2=21−5又
B
1
=
A
1
−
2
=
−
2
,
B
2
=
A
2
−
2
2
=
−
3
\;B_1=A_1-2=-2, \;B_2=A_2-2^2=-3\quad
B1=A1−2=−2,B2=A2−22=−3 (
A
1
,
A
2
A_1, A_2
A1,A2的值应该比较显然),则
{
c
1
x
1
+
c
2
x
2
=
−
2
c
1
x
1
2
+
c
2
x
2
2
=
−
3
\begin{cases} c_1 x_1+c_2 x_2=-2\\ c_1 x_1^2+c_2 x_2^2=-3 \end{cases}
{c1x1+c2x2=−2c1x12+c2x22=−3解得
{
c
1
=
−
1
2
(
3
5
+
1
)
c
2
=
1
2
(
3
5
−
1
)
\begin{cases} c_1=-\frac{1}{2}(\frac{3}{\sqrt{5}}+1)\\ c_2=\frac{1}{2}(\frac{3}{\sqrt{5}}-1) \end{cases}
{c1=−21(53+1)c2=21(53−1)即
B
n
=
−
1
2
(
3
5
+
1
)
(
1
+
5
2
)
n
+
1
2
(
3
5
−
1
)
(
1
−
5
2
)
n
\;B_n=-\frac{1}{2}(\frac{3}{\sqrt{5}}+1)(\frac{1+\sqrt{5}}{2})^n+\frac{1}{2}(\frac{3}{\sqrt{5}}-1)(\frac{1-\sqrt{5}}{2})^n
Bn=−21(53+1)(21+5)n+21(53−1)(21−5)n
则
A
n
=
2
n
−
1
2
(
3
5
+
1
)
(
1
+
5
2
)
n
+
1
2
(
3
5
−
1
)
(
1
−
5
2
)
n
(
2
)
\;A_n=2^n-\frac{1}{2}(\frac{3}{\sqrt{5}}+1)(\frac{1+\sqrt{5}}{2})^n+\frac{1}{2}(\frac{3}{\sqrt{5}}-1)(\frac{1-\sqrt{5}}{2})^n\;(2)
An=2n−21(53+1)(21+5)n+21(53−1)(21−5)n(2)
4.2 Mathematica法
命令如下:
RSolve[{a[n] == 2^(n - 2) + a[n - 1] + a[n - 2], a[1] == 0, a[2] == 1}, a[n], n]
将斐波那契数列和卢卡斯数列展开就可以得到公式(2)
(第一次碰到卢卡斯数列,刚开始一脸懵)
检查数列的项,来进行验证。
5. 回到原问题
递归计算
公式(1)已经给出了我们所需要的通项公式,但如果按第4节的解法,是解不出来的。线性代数法里会出现一个一元十次方程(菜鸡不会解这个特征方程),Mathematica则差点把我电脑烧坏(谨慎尝试,尽快放弃计算)
这个通项公式的确很难解,但我们可以绕开它:
# 递推公式法
# 投掷硬币m次 其中n次连续正面朝上的情况种数/概率
def recurrence(m=10, n=2):
# 通项公式的最开始几项
if m < n:
return 0
if m == n:
return 1
l = [0]*n
l[-1] = 1
multi = 1
# 不断向前递推
for _ in range(n, m):
multi *= 2
tmp = sum(l)+multi
del l[0]
l.append(tmp)
return l[-1], l[-1]/(2**m)
这个算法,比3.1节中的算法快很多 O ( 2 n ) → O ( n ) O(2^n) \to O(n) O(2n)→O(n)
结果
A
1000
10
A_{1000}^{10}
A100010大约
1
0
301
10^{301}
10301,这里就不列出来了。P=0.38544975241248164。
我们还是验证一下吧
- 在n很小的时候,其实很好算的(把10次正面朝上看作一个整体),然后验证recurrence函数的输出是否正确
- 更有说服力的验证,就是随机事件模拟了
# 随机事件模拟
# 投掷硬币m次 其中n次连续正面朝上的概率
def simulate(m=10, n=2, size=10000000):
count = 0
for i in range(size):
# 一次模拟出m次投掷的结果
sample = np.random.randint(0, 2, (m))
for j in range(m-n):
# 滑动长度为n的窗口 判断是否满足条件
if np.sum(sample[j:j+n]) == n:
count += 1
break
# 进度条君
if (i+1) % (size//100) == 0:
print('\r %d%%' % ((i+1)//(size//100)), end='')
print()
print(count/size)
return count/size
在size=1000000时,结果为0.384777,已经比较接近了(注意:费时操作)
6. 尾声
虽然没能得到最终的通项公式,但还是较完美地解决了这个问题。概率论真的太难了。当时我算了几个小时,还算错了,随意用Mathematica跑了一下,竟然算得十分漂亮。辛辛苦苦,还输给了一个数学软件T^T.