简单数论(学习笔记)

1.模运算:

定义:模运算为a除以m的余数,记为a mod m,有a mod m = a % m。 模运算是大数运算中的常用操作。 如果一个数太大,无法直接输出,或者不需要直接输出,可以把它取模后,缩小数值再输出。

Python虽然能直接计算大数,不用担心数据溢出,但是大数乘法太耗时,所以也常用取模来缩小数值。

一个简单应用,判断奇偶:a%2==0,a是偶数;a%2==1,a是奇数

题目:刷题统计

问题描述

小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天 做 a 道题目, 周六和周日每天做 b 道题目。请你帮小明计算, 按照计划他将在 第几天实现做题数大于等于 n 题?

输入格式

输入一行包含三个整数a,b 和 n.

输出格式

输出一个整数代表天数。

样例输入

10 20 99

样例输出

8

 题解:

a, b, n = map(int, input().split())
week = a * 5 + b * 2  #每周做题
days = (n // week) * 7  #天数
n %= week  #余数
if n <= a * 5:
    days += n // a + (n % a != 0)  #在周一到周五内
else:  #周六和周日
    days += 5
    n -= a * 5
    days += n // b + (n % b != 0)  
print(days)
#n%a!=0 如果余数不为0,则输出True,也就是1
#为0则输出False,即0

2. 快速幂

幂运算a^n,当n很大时,如果一个个地乘,时间是O(n)的,速度很慢,此时可以用快速幂,在O(logn)的时间内算出来。

快速幂的一个解法:分治法,算a^2,然后再算(a^2)^ 2,…,一直算到a^n,代码也容易写。

标准的快速幂:用位运算实现。 基于位运算的快速幂,原理是倍增。

1011  看最后一位是不是1,然后把整个数右移。现在看101,看最后一位是不是1,然后整个数右移,以此类推。。。

题目:快速幂

 

输入描述 

三个整数 b,p,k。

输入输出样例

示例

输入

2 10 9

输出

7
def fast_pow(a, n, mod):
    ans = 1
    a %= mod  #重要,防止下面的ans*a越界,凡是大数都要取模
    while n > 0:
        if n & 1 == 1:
            ans = (ans * a) % mod  #取模
        a = (a * a) % mod  #取模
        n >>= 1
    return ans

b, p, k = map(int, input().split())
print(fast_pow(b, p, k))

题目:RSA解密

 题解:

(1)求p,q

import math

n = 1001733993063167141
k = int(math.sqrt(n))
for i in range(2, k+1):
    if n % i == 0:
        print(i, n // i)
#p=891234941 q=1123984201

 (2)求e

[(d*e-1)]/[(p-1)*(q-1)] = temp

e = [temp*(p-1)(q-1)+1]/d

p=891234941
q=1123984201
tmp = (p -1)*(q - 1)
for j in range(2, n+1):
    now = j * tmp + 1  #now = d*e
    if now % d == 0:
        print(now // d)  #e = 823816093931522017
        break

(3)求

 

n = 1001733993063167141
C = 20190324
e = 823816093931522017
def fastpow(b, m, mod):
    ans = 1
    b %= mod
    while m > 0:
        if m & 1 == 1:
            ans = (ans * b) % mod
        b = (b * b) % mod
        m >>= 1
    return ans
print(fastpow(C, e, n))

3.GCD:定义、性质

最大公约数Greatest Common Divisor(GCD):整数a和b的GCD是指能同时整除a和b的最大整数,记为gcd(a, b)。由于-a的因子和a的因子相同,因此gcd(a, b) = gcd(|a|, |b|)。编码时只关注正整数的最大公约数。

性质:

(1)gcd(a, b) = gcd(a, a+b) = gcd(a, k·a+b)

(2)gcd(ka, kb) = k·gcd(a, b)

(3)定义多个整数的最大公约数:gcd(a, b, c) = gcd(gcd(a, b), c)。

(4)若gcd(a, b) = d,则gcd(a/d, b/d) = 1,即a/d与b/d互素。这个定理很重要。

(5)gcd(a+cb, b) = gcd(a, b)

手写gcd函数,常用欧几里得算法。

  辗转相除法, 又名欧几里德算法(Euclidean algorithm),是求最大公约数的一种方法。它的具体做法是:用较小数除较大数,再用出现的余数(第一余数)去除除数,再用出现的余数(第二余数)去除第一余数,如此反复,直到最后余数是0为止。如果是求两个数的最大公约数,那么最后的除数就是这两个数的最大公约数。

a=q*b+r;  都为整数               gcd(a,b)=gcd(b,r);

gcd(a,b)=gcd(b, a mod b );

辗转相除法求gcd:   

 gcd(a, b) = gcd(b, a mod b) 这是最常用的方法,极为高效。

设a > b,辗转相除法的计算复杂度为O((log2a)^3)

python实现

def gcd(a,b):
    if b == 0:
        return a
    return gcd(b,a%b)

4.LCM:定义、性质

python代码实现

def lcm(a,b):
    return a // gcd(a,b) * b

 题目:核桃的数量

 题目描述

小张是软件项目经理,他带领 3 个开发组。工期紧,今天都在加班呢。为鼓舞士气,小张打算给每个组发一袋核桃(据传言能补脑)。他的要求是:

  1. 各组的核桃数量必须相同

  2. 各组内必须能平分核桃(当然是不能打碎的)

  3. 尽量提供满足 1,2 条件的最小数量(节约闹革命嘛)

输入描述

输入一行 a,b,c,都是正整数,表示每个组正在加班的人数,用空格分开(a,b,c<30)。

输出描述

输出一个正整数,表示每袋核桃的数量。

输入输出样例

示例

输入

2 4 5

输出

20

题解:

a, b, c = map(int, input().split())
def gcd(a,b):
  if b == 0:
    return a
  return gcd(b,a%b)

def lcm(a,b):
  return a//gcd(a,b)*b
k = lcm(a,b)
print(lcm(k,c))

题目描述

Hanks 博士是 BT (Bio-Tech,生物技术) 领域的知名专家,他的儿子名叫 Hankson。现在,刚刚放学回家的 Hankson 正在思考一个有趣的问题。

今天在课堂上,老师讲解了如何求两个正整数 c1​ 和 c2​ 的最大公约数和最小公倍数。现在 Hankson 认为自己已经熟练地掌握了这些知识,他开始思考一个“求公约数”和“求公倍数”之类问题的“逆问题”,这个问题是这样的:已知正整数 a0​,a1​,b0​,b1​,设某未知正整数 x 满足:

 

  1. x 和 a0​ 的最大公约数是 a1​;

  2. x 和 b0​ 的最小公倍数是 b1​。

Hankson 的“逆问题”就是求出满足条件的正整数 x。但稍加思索之后,他发现这样的 x 并不唯一,甚至可能不存在。因此他转而开始考虑如何求解满足条件的 x 的个数。请你帮助他编程求解这个问题。

 

输入输出样例

示例 1

输入

2
41 1 96 288
95 1 37 1776 

输出

6
2

 若x是b1的因子,有 x*y = b1,y也可能是答案。 只需要在范围x<=sqrt(b1)内查询,同时判断y就行了。 但还是超时,因为gcd计算也要花时间,加上一个优化:if(b1%x==0),表示b1是x的公倍数。

题解:

import math
def gcd(a,b):
    if b == 0:
        return a
    return gcd(b, a % b)
def lcm(a,b):
    return a//gcd(a, b)*b
n = int(input())
for i in range(n):
    a0, a1, b0, b1 = map(int, input().split())
    ans = 0
    for x in range(1, int(math.sqrt(b1))+1):
        if b1 % x == 0:
            if gcd(x, a0) == a1 and lcm(x, b0) == b1:
                ans += 1
            y = b1 // x
            if x == y:
                continue
            if gcd(y, a0) == a1 and lcm(y, b0) == b1:
                ans += 1
    print(ans)

题目:寻找整数

【题目描述】有一个不超过1017的正整数n,知道这个数除以2至49后的余数如下表所示,求这个正整数最小是多少。

LCM方法

从表格的第一个数2开始,逐个增加后面的数,找满足条件的n。

1)满足第一个条件,除以2余1的数有:3、5、7、9、…此时步长k = 2。

2)继续满足第二个条件,除以3余2的数,只能从上一步骤的3、5、7、9、…中找,有5、11、17、… 此时步长k = 6,为什么k = 6?

实际上是LCM:k = lcm(2, 3) = 6

3)继续满足第三个条件,除以4余1的数,只能从5、11、17、…中找,有5、17、29、…此时步长k = lcm(2, 3, 4) = 12。

4)继续满足第四个条件,….

题解:

from  math import *
mod=[0,0,1,2,1,4,5,4,1,2,9,0,5,10,11,14,9,0,11,18,9,11,11,15,17,9,23,20,25,16,29,27,25,11,17,4,29,22,37,23,9,1,11,11,33,29,15,5,41,46] 
ans = 2 + mod[2]
k = 2                        #从第一个数的步长开始
for i in range(3,50):
    while True:
       if ans%i == mod[i]:   #ans是满足前i个数的解
          k = lcm(k,int(i))  #连续做LCM
          break
       else:  ans += k       #累加新的步长
print(ans)

5.素数的判断

素数定义:只能被1和自己整除的正整数。注:1不是素数,最小素数是2。

判断一个数n是不是素数:当n ≤ 10^14时,用试除法;n > 10^14时,试除法不够用,需要用高级算法,例如Miller_Rabin算法。

试除法:用[2, n-1]内的所有数去试着除n,如果都不能整除,就是素数。 优化:把[2, n-1]缩小到[2,  sqrt(n)]。证明:若n = a×b,其中a≤sqrt(n),b≥ sqrt(n),如果n有个因子是a,说明n不是素数,b不用再试。

题目:笨小猴

【题目描述】

笨小猴的词汇量很小,所以每次做英语选择题的时候都很头疼。但是他找到了一种方法,经试验证明,用这种方法去选择选项的时候选对的几率非常大!这种方法的具体描述如下:假设maxn是单词中出现次数最多的字母的出现次数,minn是单词中出现次数最少的字母的出现次数,如果maxn-minn是一个质数,那么笨小猴就认为这是个Lucky Word,这样的单词很可能就是正确的答案。

【输入描述】

输入文件只有一行,是一个单词,其中只可能出现小写字母,并且长度小于100。

【输出描述】

输出文件共两行,第一行是一个字符串,假设输入的的单词是Lucky Word,那么输出“Lucky Word”,否则输出“No Answer”;第二行是一个整数,如果输入单词是Lucky Word,输出maxn-minn的值,否则输出0。

题解:

from math import sqrt

letter = [0] * 26  # 统计每个字母的个数,是一个hash表

def is_prime(n):
    if n <= 1:
        return False
    for i in range(2, int(sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True

s = input().strip()
for ch in s:
    letter[ord(ch) - ord('a')] += 1

maxn, minn = -1, 1000
for i in range(26):
    if letter[i] == 0:
        continue
    if letter[i] > maxn:
        maxn = letter[i]
    if letter[i] < minn:
        minn = letter[i]

if len(s) == maxn:
    minn = 0

ans = is_prime(maxn - minn)
if not ans:
    print("No Answer\n0")
else:
    print("Lucky Word\n%d" % (maxn - minn))

例题:最大最小公倍数

【题目描述】

已知一个正整数n,问从1~N中任选出三个数,他们的最小公倍数最大可以为多少。

【输入描述】

一个正整数N。

【输出描述】

一个整数表示答案。

思路:

贪心,从大的数开始选。不过,简单地把N里面最大的三个数相乘,N*(N-1)*(N-2),并不正确,需要分析多种情况。(因为这三个数不一定互质)

最小的公倍数是三个数的质因数相乘,如果有几个质因数相同,则比较两数中哪个数的质因数的个数较多。例如6、7、8的最小公倍数,先分解因子:6=2×3,7=7×1,8=2×2×2,它们的最小公倍数是3×7×2×2×2。

大于1的两个相邻整数互质,它们没有公共的质因数。如果题目是任选二个数,最大的最小公倍数是N*(N-1)。

所以我们利用质数的性质进行思考

对于连续的三个整数,分两种情况:

(1)N是奇数。N、N-1、N-2是奇偶奇,结论是这三个数两两互质,三个数的乘积就是最大的最小公倍数。三个数两两互质,也就是说任意一个质数,只在N、N-1、N-2中出现一次。

逐个分析质数:          

质因数2,只在N-1中出现。          

质因数3,如果在N中出现(设N = 3a),就不会在N-1中出现(这要求N-1 = 3b,无解),也不会在N-2中出现(这要求N-2 = 3b,无解)。 推广到任何一个质数k,都只会在N、N-1、N-2中出现一次,所以三个数互质。

(2)N是偶数。 如果N为偶数,那么N与N-2最大公约数为2,所以我们要找下一个质数,此时需要考虑N与N-3的关系:

如果N能被3整除,则N-3也能被3整除,此时N与N-3不互质,但是N-1与N-3必然互质(N-1、N-3都为奇数)

所以N-1、   N-2、N-3 如果N不能被3整除,则N-3也不能被3整除,此时N与N-3互质,所以选择N、N-1、N-3

题解:

n = int(input())
if n <= 2:
    ans = n
elif n % 2:
    ans = n * (n - 1) * (n - 2)
else:
    if n % 3:
        ans = n * (n - 1) * (n - 3)
    else:
        ans = (n - 1) * (n - 2) * (n - 3)
print(ans)

6 素数筛

素数的筛选:给定n,求2~n内所有的素数。 一个个地判断很慢,所以用“筛子”筛所有的整数,把非素数筛掉,剩下的就是素数。 常用两种筛法:埃氏筛、欧拉筛。

埃氏筛

初始队列{2、3,4,5,6,7,8,9,10,11,12,13,...,n},操作步骤:

(1)输出最小的素数2,筛掉2的倍数,得{2,3,4,5,6,7,8,9,10,11,12,13,...} (2)输出最小的素数3,筛掉3的倍数,得{2,3,4,5,6,7,8,9,10,11,12,13,...} (3)输出最小的素数5,筛掉5的倍数,得{2,3,4,5,6,7,8,9,10,11,12,13,...}   继续以上步骤,直到队列为空。

python代码实现

primes = [0] * N
cnt = 0
bprime = [False] * N

def getPrimes(n):
    global cnt, primes, bprime
    bprime[0] = True
    bprime[1] = True
    for i in range(2, n+1):
        if not bprime[i]:
            primes[cnt] = i
            cnt += 1
            for j in range(i*2, n+1, i):
                bprime[j] = True

欧拉筛(线性的埃氏筛)

N = 1000005
primes = [0] * N
bprime = [False] * N
cnt = 0

def getPrimes(n: int):
    global cnt
    for i in range(2, n+1):
        if not bprime[i]:
            primes[cnt] = i
            cnt += 1
        j = 0
        while j < cnt and i * primes[j] <= n:
            bprime[i * primes[j]] = True
            if i % primes[j] == 0:
                break
            j += 1

7 分解质因子

分解质因子也可以用试除法。求n的质因子:

(1)第一步,求最小质因子p1。逐个检查从2到sqrt(n)的所有素数,如果它能整除n,就是最小质因子。然后连续用p1除n,目的是去掉n中的p1,得到n1。

(2)第二步,再找n1 的最小质因子。逐个检查从p1到sqrt(n1)的所有素数。从p1开始试除,是因为n1没有比p1小的素因子,而且n1的因子也是n的因子。

(3)继续以上步骤,直到找到所有质因子。

给定一个区间 [a,b],请你求出区间 [a,b] 中所有整数的质因数分解。

题目:分解质因数

输入描述

输入共一行,包含两个整数 a,b。

2≤a≤b≤10^3。

输出描述

每行输出一个数的分解,形如 k=a1​×a2​×a3​⋯(a1​≤a2​≤a3​⋯,k也是从小到大的)(具体可看样例)

输入输出样例

示例

输入

3 10

输出

3=3
4=2*2
5=5
6=2*3
7=7
8=2*2*2
9=3*3
10=2*5

 题解:

import math

p = [0] * 20  # p[] 记录因子,p[1] 是最小因子。一个 int 数的质因子最多有 10 几个
c = [0] * 40  # c[i] 记录第 i 个因子的个数。一个因子的个数最多有 30 几个

def factor(n):
    m = 0
    for i in range(2, int(math.sqrt(n)) + 1):
        if n % i == 0:
            m += 1
            p[m], c[m] = i, 0
            while n % i == 0:
                n //= i
                c[m] += 1
    if n > 1:
        m += 1
        p[m], c[m] = n, 1
    return m

a, b = map(int, input().split())
for i in range(a, b+1):
    m = factor(i)
    print(f'{i}=', end='')
    for j in range(1, m+1):
        for k in range(1, c[j]+1):
            print(p[j], end='')
            if k < c[j]:
                print('*', end='')
        if j < m:
            print('*', end='')
    print()

 

MAXN = 110
cnt = [0] * MAXN

for i in range(1, 101):
    x = i
    # 质因子分解
    j = 2
    while j * j <= x:
        if x % j == 0:
            while x % j == 0:
                x //= j
                cnt[j] += 1
        j += 1
    if x > 1:
        cnt[x] += 1

ans = 1
for i in range(1, 101):
    if cnt[i] != 0:
        ans *= (cnt[i] + 1)

print(ans)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值