📜  最小LCM的最长子序列(1)

📅  最后修改于: 2023-12-03 15:26:26.386000             🧑  作者: Mango

最小LCM的最长子序列

简介

给定一个整数序列,求它的最长子序列,使得该子序列中任意两个数的最小公倍数(LCM)最小。

解法
  1. 将整数序列进行质因数分解,对于每一个质数,若它在两个数的质因数分解中都存在,则它一定在这两个数的最小公倍数中出现。
  2. 即对于每一个质数,统计它在整数序列中的出现次数,然后组成一个出现次数的序列,对该序列求最长递增子序列,即可得到原整数序列中最小LCM的最长子序列。
实现
def lcm(a, b):
    '''
    求a,b两个整数的最小公倍数
    '''
    import math
    return a * b // math.gcd(a, b)


def prime_factorization(num):
    '''
    对整数进行质因数分解,返回一个质因数及其指数的字典
    '''
    factors = {}
    i = 2
    while i <= math.sqrt(num):
        if num % i == 0:
            if i not in factors:
                factors[i] = 1
            else:
                factors[i] += 1
            num //= i
            i = 2
        else:
            i += 1
    if num > 1:
        if num not in factors:
            factors[num] = 1
        else:
            factors[num] += 1
    return factors


def get_min_lcm_sequence(arr):
    '''
    返回整数序列arr的最小LCM的最长子序列
    '''
    primes = set()
    for num in arr:
        primes |= set(prime_factorization(num).keys())
    primes = sorted(primes)
    count_arr = [[prime_factorization(num).get(prime, 0) for num in arr] for prime in primes]
    count_arr = list(map(tuple, count_arr))
    length = len(primes)

    dp = [1] * length
    prev = [-1] * length
    for i in range(length):
        for j in range(i):
            if all(count_arr[i][k] >= count_arr[j][k] for k in range(len(count_arr[i]))):
                if dp[j] + 1 > dp[i]:
                    dp[i] = dp[j] + 1
                    prev[i] = j

    max_len_index = max(enumerate(dp), key=lambda x: x[1])[0]
    res = []
    while max_len_index != -1:
        res.append(arr[max_len_index])
        max_len_index = prev[max_len_index]
    res.reverse()

    return res
示例

给定序列 [4, 6, 8, 10, 12, 34, 45, 67, 89],最小LCM的最长子序列为 [4, 6, 10, 34, 45]

>>> get_min_lcm_sequence([4, 6, 8, 10, 12, 34, 45, 67, 89])
[4, 6, 10, 34, 45]