📅  最后修改于: 2023-12-03 15:40:13.903000             🧑  作者: Mango
这是一个算法问题,要求计算给定正整数序列中,最多选取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,或者数字分布呈现一些规律,则可以利用这些特征降低算法的时间复杂度。