进击的程序员——数学如何优化程序

最近,北大的韦神又一次冲上热搜,大体是说六个博士四个月没有解决的仿真问题,韦神一晚上搞定了。虽然韦神已经出来辟谣,说这个是假消息,但是并不妨碍我们学习韦神对待生活和工作的态度和精神。

下面我们就以一个真题目,来看一下数学是如何简化计算机程序的。

问题:有N个灯放在一排,从1到N依次顺序编号,初始状态都为关闭。有N个人,也从1到N顺序编号。1号将灯全部打开,2号将凡是2的倍数的灯全部关闭;3号将3的倍数的灯全部作相反操作(该灯如为打开,则将它关闭;如关闭,则将它打开)。以后的人,都和3号操作一样,将凡是自己序号倍数的灯作相反操作。第N个人操作完之后,一共有几盏灯亮着?

这个题目虽然相当的二,不过还是有研究的价值的。

那么我们还是先来进行蒙特卡洛模拟(暴力破解)。

使用计算机真实模拟整个过程,当全部模拟完成后,统计一下还有几盏灯亮着就可以了。

具体逻辑如下:

def create_light(num):
    l = list()
    for i in range(0,num+1):
        l.append(False)
    return l

def open_close(llst,start):
    for i in range(start,len(llst)):
        if i % start == 0:              # 满足是自己位置倍数的数
            llst[i] = not llst[i]          # 开关灯

def get_light_open(llst):
    cnt = 0
    for l in llst:
        if l:
            cnt += 1
    print("result:",cnt)

def baoli(lightlst,count):
    for i in range(1,count+1):   # 每个人依次出队
        open_close(lightlst,i)      # 每个人依次开灯关灯
        pt = ""
        for l in lightlst[1:]:
            if l:
                pt += ' ON,'
            else:
                pt += 'OFF,'
        print("%02d"%i,pt)          # 打印本次结果
    get_light_open(lightlst)       # 打印最终结果

从上面的这张结果图,我们可以从01一直看到10,并用题目所说的方法验证,可以看出整个过程无问题。检查最后一行后,可以得出10个人10盏灯,操作过后,最终会有3盏灯处于亮的状态。

到这一步,其实跟网上其他的教程都差不多,那么下面,我们就要开始用数学来重新思考这个题目了。本文提供的教程,会比网上其他的教程更深入些,其他教程分两种,要么是通过暴力分析后,通过输出找规律来说明更简便的方法;要么是泛泛地谈了下如何分析,基本没有中间的推理过程。

下面就开始我们的分析。

1. 灯的状态只有两种,亮和灭,所以某一盏灯最终的状态如何,取决于这一盏灯被操作了几次。初始状态灯都是灭的,所以某一盏灯被操作奇数次,则其最终状态为亮;被操作偶数次,其最终状态为灭。

2. 因为每一个人都是从与自己的位置对应的位置开始对灯做操作,因此经过一轮的操作不会对自己位置号之前的灯的状态有任何影响。

3. 假设位置为N的灯,都谁会操作到这盏灯呢?可以被N整除的所有数,都可以操作到这盏灯。也就是说号码是N的约数的人,都可以操作到这盏灯。

根据上面的分析,这个题就变成了一道地地道道的数学题,即N个人和N盏灯,检查从1到N的每一个数的约数的个数,个数为偶数,状态为0(灯灭);个数为奇数,状态为1(灯亮),最终找到1的个数就可以了。

再简化一下题目:给出一个数N,找到1 ~ N中的整数的每一个约数个数为奇数的数及这些数的个数。

OK,关于约数的个数,有公式如下,求一个数的因数个数方法是先把这个数分解成质数幂次相乘的形式,然后把各个质因数的幂次加一再做相乘得到。比如 24 = 2^3 * 3^1,那么24的因数个数就是(3+1)*(1+1)=8个。(本质上是排列组合,这里不做详细论述,感兴趣的可以到知乎上找找)

若N = x1^a * x2^b *.... * xn^z,则因数个数=(a+1)*(b+1)*....*(z+1)

1. 偶数*偶数=偶数,偶数*奇数=偶数,奇数*奇数=奇数

2. 因数个数为奇数,必然a,b,....z,每一个都为偶数

3. x1^a,a=2时,x1^2;a=2n时,x1^2n=(x1^n)^2,所以a为偶数,x1^a必然可以转换为xn^2形式

4. x1^a * x2^b *.... * xn^z = xa^2 * xb^2 *...* xn^2 = (xa*xb*...*xn)^2形式

有上述逻辑可以得出,因数个数为偶数,仅可能是某一个数的平方这种形式,即完全平方数(1,4,9,16......)。也就是说,整个过程中,只有完全平方数所在的位置的灯会是亮的状态。那么N前面有几个完全平方数,则有几盏灯是亮的,所以仅需要对N开平方,然后向下取整即为最终结果。

import math 
def get_light_count(N): 
		count=int(math.sqrt(N)) 
		print(N,count)

那么我们再对比下两种方法的耗时

暴力破解

开平方

100

0.0012s

0.00003.5s

1000

0.1s

0.00003.5s

10000

9.8s

0.00003.5s

这不由得又让我想起了那句话,学好数理化,走遍天下都不怕。数学果然还是优化程序最好的工具。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值