📜  最多 N 对的计数,其 LCM 不等于 Q 查询的乘积(1)

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

最多N对的计数,其LCM不等于Q查询的乘积

介绍

这是一个算法问题,要求计算给定正整数序列中,最多选取N对数,使得这N对数的最小公倍数(LCM)不等于给定值Q。

思路

一个朴素的思路是枚举所有可能的N对数,然后计算它们的LCM,再看这些LCM中是否有等于Q的。但是由于题目数据规模比较大,这个思路显然是不可行的。

我们考虑反过来想:什么样的N对数的LCM一定不等于Q呢?根据LCM的定义,显然是所有选取的数的质因数分解中,Q的质因数分解中没有出现的质因数。因为如果出现了Q的某个质因数,那么这个质因数一定在这N个数的LCM中出现,从而使得它的LCM一定包含Q。

因此我们的思路可以变成:从题目给出的正整数序列中选择M个数,使得这M个数的质因数分解中,Q的质因数分解中没有出现的质数个数最多。

于是我们可以考虑用贪心算法来解决这个子问题。具体来说,我们从大到小枚举所有质数p,然后分别统计这些数中包含p质因数的个数。设当前统计到了第i个质数,止啊你最多选j个数,使得这些数中包含了前i-1个质数的个数一定小于p的个数。则j个数中包含p的个数就是j个数中包含前i个质数中p的个数的最大值。

具体见下文的代码实现部分。

代码实现
from collections import Counter
from functools import reduce
from operator import mul
def solve(arr, Q, N):
    """
    :param arr: positive integer sequence, list of integers
    :param Q: an integer
    :param N: an integer
    :return: the count of selecting N tuples, whose LCM is not equal to Q, int
    """
    primes = get_primes(Q)
    cnt = Counter()
    for x in arr:
        prime_factors = get_prime_factors(x, primes)
        for p in prime_factors:
            cnt[p] += 1
    candidates = sorted(primes, key=lambda p: cnt[p], reverse=True)
    ans = 0
    for i in range(N+1):
        if i > len(candidates): break
        selected = candidates[:i]
        rest = list(filter(lambda p: p not in selected, candidates))
        cnt1 = count_factors(arr, selected)
        cnt2 = count_factors(arr, rest)
        if LCM(selected) % Q != 0: ans += C(cnt2, N-i)
        if N-i >= cnt1[0] and LCM(selected+cnt1[1][:N-i-cnt1[0]]) % Q != 0:
            ans += C(cnt2, N-i-cnt1[0])
    return ans

def get_primes(Q):
    """
    Get all prime factors of Q
    """
    primes = []
    for d in range(2, Q):
        if Q % d == 0:
            primes.append(d)
            while Q % d == 0: Q //=d
    if Q > 1: primes.append(Q)
    return primes

def get_prime_factors(x, primes):
    """
    Get all prime factors of x and return a set.
    """
    ans = set()
    for p in primes:
        if x%p == 0:
            ans.add(p)
            while x%p == 0: x//=p
    return ans

def count_factors(arr, primes):
    """
    Count the number of elements containing each prime factor.
    :return: (n, factors)
        n: the number of trailing non-selected elements with non-zero factor
        factors: all elements with non-zero factor
    """
    cnt = Counter()
    ans = []
    for x in arr:
        prime_factors = get_prime_factors(x, primes)
        ans.append((x, prime_factors))
        for p in prime_factors:
            cnt[p] += 1
    trailing = 0
    while trailing < len(ans) and ans[-trailing-1][1].intersection(primes[-1:]) == set(primes[-1:]):
        trailing += 1
    return trailing, ans[-trailing:] if trailing < len(ans) else ans

def LCM(arr):
    """
    Compute the LCM of a sequence of numbers
    """
    lcm = 1
    for p, k in Counter(reduce(lambda x, y: x+y, [get_prime_factors(x, []) for x in arr])).items():
        lcm *= p**k
    return lcm

def C(arr, k):
    """
    Compute the number of combinations to select k elements from arr.
    """
    ans = 1
    for a, b in arr:
        ans *= comb(b, min(a, k))
        k -= min(a, k)
        if k == 0: return ans
    return 0

def comb(n, k):
    """
    Compute the number of combinations of k from n.
    """
    ans = 1
    for i in range(k):
        ans *= (n-i)
        ans //= (i+1)
    return ans
复杂度分析

算法的时间复杂度是O(NPZlogZ),其中Z是所有数字中的最大值。在实际应用中,需要对P个质数进行排序,这可能会使得算法的运行时间增加一些。如果题目数据有特殊规律,比如数字中只包含1、2、3,或者数字分布呈现一些规律,则可以利用这些特征降低算法的时间复杂度。