前言:
上篇博客线性筛法求素数(欧拉筛法)讲解了数论的常用算法欧拉筛法,本篇博客主要讲解算术基本定理,并以例题详细说明。
题目:
题目链接:X的因子链
输入正整数 X,求 X 的大于 1 的因子组成的满足任意前一项都能整除后一项的严格递增序列的最大长度,以及满足最大长度的序列的个数。
输入格式
输入包含多组数据,每组数据占一行,包含一个正整数表示 X。
输出格式
对于每组数据,输出序列的最大长度以及满足最大长度的序列的个数。
每个结果占一行。
数据范围
1≤X≤
2
20
2^{20}
220
输入样例:
2
3
4
10
100
输出样例:
1 1
1 1
2 1
2 2
4 6
思路:
首先理解题目意思,这里以12 为例说明,12 的大于1的因子包含[ 2,3,4,6,12],题目描述的条件是 a[i] % a[i - 1] == 0,所以12 可以组成的最大长度的序列有[2,4,12] 和[3,6,12] 还有[2,6,12],最大长度为3,能组成最大长度为3的序列个数有3个。
这道题需要用到算术基本定理:
就是因式分解的定理,所有的整数都可以唯一分解成若干个质因子乘积的形式:
由题意可知,组成的数组必然满足a[i + 1] =a[i] * q (q是倍数且大于一,每次的q可以为任意大于1的值) 每一个后一项都等于前一项乘上一个倍数,那么我们要想让整个序列最长的话,要尽可能让倍数最小,最小只能小到质数,因为小到质数就不能再分了,因此就可以用算术基本定理了和求解最小质因子。
最小质因子的求解和质数的求解见上篇博客:线性筛法求素数(欧拉筛法)(求质数,O(n)时间复杂度)(外加求每个整数的最小质因子)(python))
- 假设N分解质因数的结果如上图所示,我们可以发现一共有 α 1 \alpha_1 α1 + α 2 \alpha_2 α2 + … + α k \alpha_k αk 个质因子,因此我们每一次后一项要比前一项至少要多一个倍数,每一次的倍数必然是一个质数,必然是在X当中的某一个质因子,所以序列的最大长度就是 α 1 \alpha_1 α1 + α 2 \alpha_2 α2 + … + α k \alpha_k αk
例:
12 所满足的最大长度为 2 + 1 = 3
36 所满足的最大长度为 2 + 2 = 4
105 所满足的最大长度为 1 + 1 + 1= 3
最大长度求出来了那么组成最大长度的序列如何求呢?
组成最大长度的序列数其实就是排列组合问题
- 假设x的质因子个数为n,并且所有的质因子都不相同,那么一共会有 n! 组合,但实际情况是质因子可能会相同,那么我们需要额外统计一下,每个质因子是否有多个,求出每个质因子的个数,用 n! 除以每个质因子个数的阶乘,就是最大长度的序列数的答案,因为我们把相同的排列可能去掉了。(主要排列数公式见下图右下方,该公式被称为多重集合的排列数问题,这样就避免了重复情况。)
通俗易懂的方法来讲(以上述例子说明)
对于12来说,12 = 2 2 2^2 22 ∗ \ast ∗ 3, 所有可能组合数是(最大长度) 3 的阶层 除以 2的阶层(2 出现了 2次)再除以 1 的阶层(3 出现了1次)答案是 (3 ∗ \ast ∗ 2 ∗ \ast ∗ 1) ÷ \div ÷ (2 ∗ \ast ∗ 1 ∗ \ast ∗ 1) = 3
同理 36 的最大长度的序列数 是 4! ÷ \div ÷ (2! ∗ \ast ∗ 2!) = 6
代码及详细注释:
n = 2 ** 20 + 1 # 设置一个较大的数作为上限
prime = [0] * n # 创建一个长度为n的数组prime,用于存储质数
vis = [False] * n # 创建一个长度为n的数组vis,用于标记是否被访问过
cnt = 0 # 初始化计数器为0
st = [0] * n # 创建一个长度为n的数组st,用于记录最小质因数
fact = [0] * 30 # 创建一个长度为30(长度可自定义)的数组fact,用于存储质因数
frequency = [0] * n # 创建一个长度为n的数组frequency,用于存储每个质因数的次数
for i in range(2, n):
if not vis[i]:
prime[cnt] = i # 将当前数 i 记录为质数
st[i] = i # 当前数 i 的最小质因数为自身
cnt += 1
for j in range(cnt):
if prime[j] * i >= n: # 如果超出n 则无需后续操作直接退出循环
break
st[prime[j] * i] = prime[j] # 将 i*prime[j] 的最小质因数标记为 prime[j]
vis[prime[j] * i] = True # 标记 i*prime[j] 已经被筛过
# 如果i是前面某个素数的倍数时, 说明i以后会由某个更大的数乘这个小素数筛去同理,
# 之后的筛数也是没有必要的, 因此在这个时候, 就可以跳出循环了
if i % prime[j] == 0:
break
# 循环读入输入,并计算质因数分解
while True:
try:
x = int(input()) # 读入一个整数
k = 0
total = 0
# 依次处理各个质因子, 求出对应质因子出现的次数
while x > 1:
q = st[x] # 通过while, 依次取出最小质因子
fact[k] = q # 记录质因数
frequency[k] = 0 # 初始化质因数的次数为0
while x % q == 0: # 处理当前质因子, 求其出现的次数
x = x // q
frequency[k] += 1 # 统计质因数的次数
total += 1 # 统计不同质因数的个数
k += 1 # 准备处理下一个质因子
res = 1
# 求所有质因子出现总次数的全排列
for i in range(1, total + 1):
res = res * i
# 去除各个质因子重复出现的次数
for i in range(k):
for j in range(1, frequency[i] + 1):
res = res // j
print(total, res) # 输出最长序列的长度, 以及满足最大长度的序列的个数
except EOFError:
break
总结:
本题还是比较有难度的,自己也是花费了两小时才搞懂。