Greatest Common Divisor(gcd性质)
题意
给定\(n\)个正整数\(a_i\),问它们至少同时加多少,可以使得它们的最大公约数大于\(1\)。
若不存在,则输出\(-1\)
数据范围
\(1 \leq T \leq 20\)
\(1 \leq n \leq 10^5\)
\(1 \leq a_i \leq 10^9\)
思路
最大公约数性质
- 更相减损术(引用李煜东《算法竞赛进阶指南》)
\(\forall a, b \in \mathbb{N}, a \geq b\),有\(gcd(a, b) = gcd(b, a - b) = gcd(a, a - b)\)
\(\forall a, b \in \mathbb{N}\),有\(gcd(2a, 2b) = 2gcd(a, b)\)
证明:
根据最大公约数的定义,后者显然成立,我们主要证明前者。
对于\(a, b\)的任意公约数\(d\),因为\(d|a, d|b\),所以\(d|(a - b)\)。因此\(d\)也是\(b, a - b\)的公约数,反之也成立。故\(a, b\)的公约数集合与\(b, a - b\)的公约数集合相同。于是,它们的最大公约数自然也相等。对于\(a, a - b\)同理。
- 推论
\(\forall a, b, c \in \mathbb{N}, a \leq b \leq c\),有\(gcd(a, b, c) = gcd(a, b - a, c - b)\)
证明:
\(gcd(a, b, c) = gcd((a, b), (b, c)) = gcd((a, b - a), (b, c - b)) = gcd(a, b - a, b, c - b)\)
由于\(gcd(a, b - a) = gcd(b, b - a)\),则\(gcd(a, b - a, b, c - b) = gcd(a, b - a, c - b) = gcd(a, b, c)\)。
本题做法
我们再回到本题中来,这道题的最终目标就是\(gcd(a_1 + k, a_2 - a_1, a_3 - a_2,\dots, a_n - a_{n - 1}) > 1\)
我们可以将其拆成两部分,第一部分是\(a_1 + k\),第二部分是\(a_2 - a_1, a_3 - a_2, \dots, a_n - a_{n - 1}\)
若后一部分的最大公约数是\(1\),那么两部分的最大公约数就肯定是\(1\),这种情况就是无解的。
若后一部分的最大公约数是\(x, x \ne 1\),那么我们就考察\(x\)及其约数与第一部分的关系。我们不妨设\(x\)及其约数为\(q\),我们需要找的是满足\(a_1 + k = sq, s > 1\)的最小值。
由\(a_1 = sq - k\)可得,\(k = \lceil a_1 / q \rceil * q - a_1\)
最后要注意的一点是所有数相同的情况,若所有数都是\(1\),那么输出\(1\);若都不是\(1\),那么输出\(0\)
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = 100010;
int n;
ll a[N];
ll gcd(ll a, ll b)
{
return b ? gcd(b, a % b) : a;
}
int main()
{
int T;
scanf("%d", &T);
for(int cas = 1; cas <= T; cas ++) {
scanf("%d", &n);
ll sum = 0;
for(int i = 1; i <= n; i ++) {
scanf("%lld", &a[i]);
sum += a[i];
}
if(sum == n) {
printf("Case %d: 1\n", cas);
continue;
}
ll tmp = a[1];
for(int i = 2; i <= n; i ++) tmp = gcd(tmp, a[i]);
if(tmp > 1) {
printf("Case %d: 0\n", cas);
continue;
}
sort(a + 1, a + n + 1);
tmp = a[2] - a[1];
for(int i = 2; i < n; i ++) tmp = gcd(tmp, a[i + 1] - a[i]);
if(tmp == 1) {
printf("Case %d: -1\n", cas);
continue;
}
ll ans = ((a[1] - 1) / tmp + 1) * tmp - a[1];
for(ll i = 2; i <= tmp / i; i ++) {
if(tmp % i) continue;
ll k = tmp / i;
ans = min(ans, ((a[1] - 1) / i + 1) * i - a[1]);
ans = min(ans, ((a[1] - 1) / k + 1) * k - a[1]);
}
printf("Case %d: %d\n", cas, ans);
}
return 0;
}