📅  最后修改于: 2023-12-03 15:28:28.004000             🧑  作者: Mango
在算法竞赛中,有一类经典问题叫做 "通过递增或递减 K 来最大化数组的 GCD",是一道经典的数学问题,如果你对数论有一定的了解,应该会容易理解。
下面,我们将为您介绍该问题以及如何用代码来解决它。
给定一个长度为 N 的数组 A,和一个整数 K。你可以使得数组 A 中的每个元素加上或减去 K,使得数组 A 的 GCD 值最大化。求这个最大的 GCD 值。
经典问题,本题解中提供了 O(N lg C) 的做法,其中 C 是元素最大值。
对于数 x,设 A 中最接近 x 与 x 的差为 t。对于 GCD(A+k),需要在 [x-t, x+t] 中找一个最大的 d,使得 d|A[i]+k (1 ≤ i ≤ n),这个问题有一个很明显的子问题,就是当 d 和 A[i] 模 mod=d 的余数相同时,GCD(A+k) 对 d 的贡献一样。所以可以把 A 中每个数和 x 模 d 的余数组成的桶扫一遍,维护所有的前缀和,计算出每个模数的数的 sum,然后用 sum 是否为 0 来判断这个模数是否是 A 中的因子。
这个问题的关键在于如何快速地计算出所有模数的 sum,这个问题可以考虑差分数组,为了得到 k 的答案,需要用到 t=(x mod k)和 k-t 两组数的差分,这样就能得到所有以 k 为模数的数的 sum。
在计算答案时,可以枚举 GCD 的因子,对于一个因子 k,如果存在 A 中的一个数与 x 的差 mod k 等于 t,那么 k 就是可能的答案。具体实现时可以先枚举 k,然后在 k 的所有倍数中二分找到最大的小于等于 x 的数 y,用 abs(x-y) 作为 t。计算出所有以 k 为因子的数的 sum,计算方案数即可。
代码实现分为以下几个部分:
代码实现如下:
from math import sqrt
def gcd(x, y):
if x < y:
x, y = y, x
while y:
x, y = y, x % y
return x
def check(ans, x, A, t, n):
for k in range(1, int(sqrt(ans)) + 1):
if ans % k != 0:
continue
for ku in range(x % k, n, k):
if abs(t[A[ku] % ans]) != -1:
return False
return True
def work(A, x, n):
t = [-1] * x
for i in range(n):
t[A[i] % x] *= -1
pre = 0
for i in range(1, x):
pre += t[i-1]
t[i] *= pre
sum = 0
for i in range(1, x):
if x % i == 0:
sum += t[i]
return sum
def solve(A, n, x):
ans = 1
if x == 2:
return 2
for p in range(0, int(sqrt(x))+1):
if x % (p+1) == 0:
t1 = p+1
t2 = x//(p+1)
for i in range(-1, 2, 2):
for j in range(-1, 2, 2):
d1 = abs(i*t1-j*t2)
if d1 <= 1:
continue
if work(A, d1, n) == 0:
if check(ans, x, A, [0]+list([float('nan')]*d1), n):
ans = d1
return ans
这是一道经典的数论问题,考察了如何计算 GCD 的因子及其倍数,同时也考察了如何根据差分数组计算区间和的问题。