CCF CSP认证 20231202 因子化简 满分题解 + 标准题解 Python实现

因子化简

一、概述

  本题目结合素数求解,按特定幂次要求,对整数进行因子分解,考察常见算法(素数求解)、降低时间复杂度的方法,难度简单。真题跳转官网查看
  真题来源: 因子化简
  官网地址:www.cspro.org(模拟考试入口)

二、题目分析

  输入 q 组整数 n i n_i ni和权重 k i k_i kii=1,2,…,q;求每组整数的素数因子 p 1 , p 2 , . . . , p m p_1,p_2,...,p_m p1,p2,...,pm,使该整数按素数幂的积表示,即 n = p 1 t 1 × p 2 t 2 × . . . × p m t m n = p_1^{t_1} \times p_2^{t_2} \times ... \times p_m^{t_m} n=p1t1×p2t2×...×pmtm;将整数依照每组的权重 k i k_i ki,剔除 < k i <ki的素数因子;输出剩余项的乘积,若无剩余项则输出1

三、满分题解

1. 题解一(就题解题)

  • 第一步,接受一个正整数,它表示查询个数 q 。接受 q 组输入。每组输入包含正整数 nkn 为要查询的整数,k权重。时间复杂度为 O ( n ) O(n) O(n)
  • 第二步,求素数因子,需要判断一个数是否为素数。首先明白概念,素数,指的是在大于1的自然数中,除了1和它本身以外不再有其他因数的数。换句话说,素数是只有两个正因数(1和自身)的自然数。 定义 函数is_prime(num) ,接受参数 num ,是素数返回 True ,否则返回 False 。当接受的参数 n u m ≤ 1 num \leq 1 num1,则一定不是素数;当 n u m = = 2 num==2 num==2,一定是素数;对于其他数:如果一个数不是素数,那么它一定能被它的某个因数整除,而这个因数又不大于它的平方根。所以,因数检查到平方根就可以了。只需要写个循环,若 num 能被因数 i 整除,则 num 不是素数,返回 False 。若 [2,num平方根] 检查结束, num 也未被某个因数整除,则 num 是素数,返回 True 。时间复杂度为 O ( n ) O(\sqrt n) O(n )
  • 第三步,求 n素数因子及各素数因子的。由题子任务的描述可知 1 < n ≤ 1 0 10 11<n1010,并且最终简化后若无剩余项则输出1。每一个合数(非素数)都可以唯一地表示为有限个素数的乘积,若有一素数> 1 0 5 10^{5} 105,那该素数的倍数也会> 1 0 10 10^{10} 1010。并且,k>1,因此,直接求10000以内的素数因子。根据数学运算法则,按素数因子从小到大依次求素数因子的指数,剔除非因子的素数。得到包含素数因子:因子对应的指数的字典。函数prime_item(num),时间复杂度为 999998 × O ( n ) + O ( m a x ( 幂次 ) n ) + 2 O ( n ) 999998\times O(\sqrt n)+O(max(幂次)n) + 2O(n) 999998×O(n )+O(max(幂次)n)+2O(n)
  • 第四步,因子简化,计算输出值。定义计算初始值mlc1,遍历素数因子键值对,若值>=k,则mcl累乘幂指数即可。时间复杂度为 O ( n ) O(n) O(n)
  • 第五步,格式化输出。输出mlc,时间复杂度为 O ( 1 ) O(1) O(1)
1.1 编码实现
def is_prime(num):      # 判断是否为素数
    if num <= 1:
        return False
    if num == 2:
        return True
    for i in range(2, int(num ** 0.5)+1):
        if num % i == 0:
            return False
    return True


def prime_item(num):      # 求素数因子:因子对应的指数
    # 求 2到sqrt(10**10)的素数因子
    prime_li = []
    prime_dic = {}
    for i in range(2, 100000):   # 因子
        if is_prime(i):
            prime_dic[i] = 0
            prime_li.append(i)
    # 求素数因子的指数
    for prime in prime_li:
        while num % prime == 0:
            prime_dic[prime] += 1
            num = num / prime
        if num == 1:
            break
    # 处理非因子的素数
    del_key = []
    for key in prime_dic.keys():
        if prime_dic[key] == 0:
            del_key.append(key)
    for key in del_key:
        del prime_dic[key]
    return prime_dic


if __name__ == "__main__":
    q = int(input())    # 查询个数
    for i in range(q):
        mlc = 1     # 乘积结果
        n, k = map(int, input().split())    # n:整数 k:阈值
        primes = prime_item(n)
        for item in primes.items():
            if item[1] >= k:
                mlc *= item[0]**item[1]
        print(mlc)

1.2 提交结果

请添加图片描述

1.3 优化题解一

  在题解一中,prime_item(num) 函数执行了 q 次,所以求 2 → 1 0 5 2 \rightarrow 10^{5} 2105的素数因子也执行了 q 次,实际上只需一次生成 prime_li 即可。

1.3.1 编码实现
def is_prime(num):      # 判断是否为素数
    if num <= 1:
        return False
    if num == 2:
        return True
    for i in range(2, int(num ** 0.5)+1):
        if num % i == 0:
            return False
    return True


def get_prime_li():     # 求2到sqrt(10**10)的素数因子
    prime_li = []
    for i in range(2, 100000):   # 因子
        if is_prime(i):
            prime_li.append(i)
    return prime_li


def get_prime_dic(num, prime_li):      # 求素数因子:因子对应的指数
    prime_dic = {}
    for p in prime_li:     # 初始化
        prime_dic[p] = 0
    # 求素数因子的指数
    for prime in prime_li:
        while num % prime == 0:
            prime_dic[prime] += 1
            num = num / prime
        if num == 1:
            break
    # 处理非因子的素数
    del_key = []
    for key in prime_dic.keys():
        if prime_dic[key] == 0:
            del_key.append(key)
    for key in del_key:
        del prime_dic[key]
    return prime_dic


if __name__ == "__main__":
    q = int(input())    # 查询个数
    prime_li = get_prime_li()
    for i in range(q):
        mlc = 1     # 乘积结果
        n, k = map(int, input().split())    # n:整数 k:阈值
        primes = get_prime_dic(n, prime_li)
        for item in primes.items():
            if item[1] >= k:
                mlc *= item[0]**item[1]
        print(mlc)

1.3.2 提交结果

请添加图片描述

2. 题解二(通解)

  可将本题看作是三个问题。问题一:判断整数n是否为素数;问题二:求整数n以内的所有素数;问题三:求n的因子分解形式,素数因子的乘积。

  • 问题一:由于因子总是程度出现的。例如: n % a ≡ 0 ⇔ n ≡ a ∗ b n \% a \equiv 0 \Leftrightarrow n \equiv a *b n%a0nab 则有 m i n ( a , b ) ≤ n ≤ m a x ( a , b ) min(a,b) \leq \sqrt n \leq max(a,b) min(a,b)n max(a,b) 。因此只需检查 [   2 , n   ] [\ 2,\sqrt n \ ] [ 2,n  ] 内是否有 n 的因子即可,若有,则 n 不是素数返回 False ;无,则 n 是素数,返回 True 。时间复杂度为 O ( n ) O(\sqrt n) O(n )

    def is_prime(num):      # 判断num是否为素数
        for i in range(2, int(num ** 0.5)+1):
            if num % i == 0:
                return False
        return True
    
  • 问题二:利用埃拉托斯特尼筛法,对于任意大于1的自然数**x,kx(k>1)**是合数。由于因数因数是成对存在的,所以只需要筛选出所有 1 2 n u m {1 \over 2} num 21num以内的素数。时间复杂度为 O ( 1 2 n n ) O({1 \over 2}n\sqrt n) O(21nn )

    def get_primes(num):     # 素数筛选算法,求num折半以内的所有素数
        prime_li = [True] * (num + 1)   # 初始化:设0-num所有数为素数
        for i in range(2, num//2 + 1):   # 对任意大于1的整数i,ji(j>1)是合数
            for j in range(2, num//i + 1):   # 筛除i的倍数
                prime_li[j * i] = False
        return prime_li
    
  • 问题三:求 n 的因子分解形式。结合问题二求素因子考虑,无需求出 n 所有的素因子,将 ≤ n \leq \sqrt n n 的素因子除去,剩余项 = 1即可。利用元组存储**(素因子,幂)。具体执行时,利用埃拉托斯特尼筛法,在找到一个整数 n 的因子 i 后,将 in 中除去,保证每个 i 都是素因子**,除法执行的次数就是幂次。时间复杂度为 O ( n ) O(\sqrt n) O(n )

    def decompose(num):   # 将整数num用因子形式表示 (因子,幂)
        ans = []
        i = 2
        while i * i <= num:   # 检查2-sqrt(n)
            tmp = 0
            while num % i == 0:   # 素数筛选算法, 筛掉i的倍数,每筛一次,i的幂次+1
                tmp += 1
                num //= i
            if tmp > 0:
                ans.append((i, tmp))
            i += 1
        if num > 1:   # 大于sqrt(n)的素因子最多只有1个
            ans.append((num, 1))
        return ans
    
2.1 编码实现
def decompose(num):   # 将整数num用因子形式表示 (因子,幂)
    ans = []
    i = 2
    while i * i <= num:   # 检查2-sqrt(n)
        tmp = 0
        while num % i == 0:   # 素数筛选算法, 筛掉i的倍数,每筛一次,i的幂次+1
            tmp += 1
            num //= i
        if tmp > 0:
            ans.append((i, tmp))
        i += 1
    if num > 1:   # 大于sqrt(n)的素因子最多只有1个
        ans.append((num, 1))
    return ans


if __name__ == "__main__":
    q = int(input())    # 查询个数
    for i in range(q):
        mlc = 1     # 乘积结果
        n, k = map(int, input().split())    # n:整数 k:阈值
        num_primes = decompose(n)    # 求n以内的所有素数因子,并用因子形式表示
        for item in num_primes:
            if item[1] >= k:
                mlc *= item[0]**item[1]
        print(mlc)

2.2 提交结果

请添加图片描述

四、小结

  第32、31、30届CSP的第二题,更倾向于基础数学知识应用方面的模拟,对降低时间复杂度以及对子任务及其条件理解程度的考察。本题目若能够合理利用 1 这个条件,结合每一个合数(非素数)都可以唯一地表示为有限个素数的乘积这个定理,题解的确会有效降低时间复杂度;若还知道埃拉托斯特尼筛法,the Sieve of Eratosthenes algorithm( 素数筛选算法 ),那本题就特别简单了。

  上述题解的时间复杂度如下:

  • O 标准题解 = O ( n ) × [ O ( m a x ( 幂次 ) n ) + O ( 1 ) ) ≈ O ( n n ) O_{标准题解} = O(n) \times [O(max(幂次)\sqrt n) + O(1)) \approx O(n \sqrt n) O标准题解=O(n)×[O(max(幂次)n )+O(1))O(nn )

  • O 题解一优化 = O ( n ) × { O ( n ) + O [ m a x ( 幂次 ) n ] + O ( 1 ) } ≈ O ( n 2 ) O_{题解一优化} = O(n) \times \{O(\sqrt n)+O[max(幂次)n]+O(1)\} \approx O(n^2) O题解一优化=O(n)×{O(n )+O[max(幂次)n]+O(1)}O(n2)

  • O 题解一 = O ( n ) × { 999998 × O ( n ) + O [ m a x ( 幂次 ) n ] + 2 O ( n ) + O ( 1 ) } ≈ m a x { 999998 × O ( n n ) , [ m a x ( 幂次 ) + 2 ] × O ( n 2 ) } O_{题解一} = O(n) \times \{999998\times O(\sqrt n)+O[max(幂次)n] + 2O(n) + O(1)\} \approx max\{999998 \times O(n \sqrt n),[max(幂次)+2] \times O(n^2)\} O题解一=O(n)×{999998×O(n )+O[max(幂次)n]+2O(n)+O(1)}max{999998×O(nn ),[max(幂次)+2]×O(n2)}

你可能感兴趣的:(CCF,CSP认证,python,开发语言)