儿童节快乐,聊聊数字的规律和同余原理
某年的6月1日是星期日。那么,同一年的6月30日是星期几?
星期是7
天一个循环。所以说,这一天是星期几,7
天之后同样也是星期几。而6
月30
日是在6
月1
日的29
天之后:29 ÷ 7 = 4 ... 1
用29
除以7
,可以得出余数为1
。而6
月1
日那一天是星期日,那么,6
月30
日就是星期日的后一天,也就是星期一。
某年的3月,Wendy想制定一个实习计划,它想知道当年的7月和11月有哪几天是星期天。但月历坏了,5月后面的几页看不到了。Wendy却轻松找到了想要的日期,难道Wendy是天才喵?
不是,因为它知道,每一年的3
月1
日到3
月30
日,与11
月1
日到11
月30
日,在星期上面是完全相同的。同样,4
月1
日到4
月30
日,与7
月1
日到7
月30
日,在星期上面也是完全相同的(31
日除外)。
这到底是怎么一回事呀?
每一年过了3
月之后,在天数上,4
月、6
月、9
月、11
月都是30
天,而其他的月份都是31
天。
把3
月到10
月的天数相加,得出:31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 = 245
。
245 ÷ 7= 35
,用245
除以一周的天数7
天,就可以得出,3
月1
日~3
月30
日,与11
月1
日~11
月30
日,在星期上面是完全相同的。
同样,把4
月到6
月的天数相加,得出:30 + 31 + 30 = 91
。
91 ÷ 7 = 13
,就可以得出,4
月1
日到4
月30
日,与7
月1
日到7
月30
日,在星期上面也是完全相同的。
原来,数字有规律性和周期性。高斯于是提出了“同余式”的理论。
当a
除以m
所得余数,和b
除以m
所得余数相同的时候,就将它写成:a ≡ b(mod m)
。称为整数a
,b
对模m
同余。而上面的这个算式就是同余式。在这里,mod
表示整除后取余的意思。同C
语言的%
运算符的功能是一样的。
比如3 ≡ 1 (mod 2)
,27 ≡ 2 (mod 5)
。
同余式可以相加。
比如,当除以7
的时候,10
和17
的余数为3
,9
和16
的余数为2
:10 ≡ 17(mod 7)
,9 ≡ 16 (mod 7)
。
将10 + 9
和17 + 16
分别再除以7
,得出的余数都是3 + 2 (5)
,也就是说:10 + 9 ≡ 17 + 16 (mod 7)
。
不光是相加,相减,或者相乘(乘方),同余式依然成立。
同余式和其他的等式一样,可以进行加法、减法、乘法(乘方)的运算。
求:13的2000次方除以12
13 ≡ 1 (mod12)
,根据同余式的性质④,能够得出:13^2000 ≡ 1^2000 ≡ 1 (mod 12)
。余数为1
。
求:斐波那契数列中是3的倍数的项
如果用通项求:
可能不够优雅。
不妨根据这个数列的递推方程式,求出一开始几项的具体数字,然后将这些数字除以3
,得出余数。
我们想要找到整数除法运算当中“余数”的周期和规律。
假设:
a n + 1 = 3 p + k a_{n+1}=3p+k an+1=3p+k
a n = 3 q + l a_{n}=3q+l an=3q+l
k
和l
都是0
,1
,2
三个数字当中的某个整数。
an+1
除以3
的余数为k
,而k
除以3
的余数也为k
,可以得出: a n + 1 ≡ k ( m o d 3 ) a_{n+1} ≡ k (mod3) an+1≡k(mod3)。
同样,an
除以3
的余数为l
,而l
除以3
的余数也为l
,可以得出: a n ≡ l ( m o d 3 ) a_{n} ≡ l (mod3) an≡l(mod3)。
根据递推方程式: a n + 2 = a n + 1 + a n a_{n+2} = a_{n+1} + a_n an+2=an+1+an
根据同余式的性质①,得出: a n + 2 ≡ a n + 1 + a n ≡ k + l ( m o d 3 ) a_{n+2} ≡ a_{n+1} + a_n ≡ k+l (mod 3) an+2≡an+1+an≡k+l(mod3)。
也就是说,想要求出an+2
除以3
的余数,就必须先求出an+1
和an
除以3
的余数,然后将两个余数相加。
通过先前的表格,我们可以看出,如果连续2
个余数和前面的某2
个余数相同的话,那么,此后的余数都按照这个形式循环下去。
比如,第9
项和第10
项的余数与第1
项和第2
项的余数相同,所以当这个数列除以3
的时候,所得出的余数的周期为8
。
一开始的8
项当中,a4
和a8
能够被3
整除。
所以,n
的条件为,n
是4
的倍数:4,8,12,16,20,24,28,32,···
。
线性同余法获取伪随机数
在C
语言中,只要调用rand()
函数,就可以得到随机数。不过,由于借助公式产生的随机数具有一定的规律性,因此并不是真正的随机数,通常称为伪随机数。
线性同余法:如果把Ri
作为当前随机数的话,那么下一个出现的随机数Ri + 1
就是, R i + 1 = ( a × R i + b ) m o d c R_{i+1}=(a × R_i+b) mod c Ri+1=(a×Ri+b)modc
对a
、b
、c
各参数设定合适的整数后,可以从该公式获得的随机数的范围就是0
到c
(不包含)。例如,把a
设定为5
,b
设定为3
,c
设定为8
,Ri
的初始值定为了1
。
这些随机数确实很像是无规则随机出现的数值。不过,产生8
次随机数后,下8
次产生的随机数就和前面的数值相同了。这种周期性是伪随机数的特征,也是为什么不是真随机数的原因。
srand(time(NULL));
可以提前设定Ri
、a
、b
、c
的数值。srand()
函数中的参数time(NULL)
,是用来获取当前时间的参数。由于每次启动程序时的当前时间都是变化的,因此Ri
、a
、b
、c
的数值也会随之发生变化。Ri
、a
、b
、c
的数值就称为随机数的种子。
而重复调用rand()
函数的话,因为Ri
、a
、b
、c
的数值都有默认值,因此每次都会生成以相同方式出现的随机数。
参考
[1] 写给全人类的数学魔法书
[2] 程序是怎样跑起来的