P r o b l e m \mathrm{Problem} Problem
给定 n n n 个数。每次可以选择将一个数 + 1 +1 +1 或 − 1 -1 −1 。求至少多少次操作使得整个序列全部元素的 gcd > 1 \gcd >1 gcd>1 。
n ≤ 1 0 5 , a i ≤ 1 0 12 n\leq 10^5,a_i\leq 10^{12} n≤105,ai≤1012 。
S o l u t i o n \mathrm{Solution} Solution
首先由一个性质:答案不会超过 n n n.
- 因为无论如何我们都可以通过小于等于 n n n 次操作转化为所有数字都是 2 2 2的倍数。
有上述结论可知,至少有一半的数字操作次数小于 2 2 2次。
- 若有超过一半的数字操作次数超过 2 2 2 次了,那么操作次数一定大于 n n n,与上述矛盾。
因此我们观察到有一半的性质,可以联想到随机化算法。
- 每一次都随机一个数,那么答案一定是这个数 x ( ± 1 ) x(±1) x(±1)的因数。
- 每一次我们的正确率为 1 2 \frac{1}{2} 21,因此循环 k k k次以后正确率为 1 − 1 2 k 1-\frac{1}{2^k} 1−2k1.
时间复杂度: O ( n k ) O(nk) O(nk).
C o d e \mathrm{Code} Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 3e5;
int n;
int a[N], cnt[N];
int read(void)
{
int s = 0, w = 0; char c = getchar();
while (c < '0' || c > '9') w |= c == '-', c = getchar();
while (c >= '0' && c <= '9') s = s*10+c-48, c = getchar();
return w ? -s : s;
}
int Get(int x)
{
int sum = 0;
for (int i=1;i<=n;++i)
{
if (a[i] < x) sum += x - a[i];
else sum += min(a[i] % x, x - a[i] % x);
}
return sum;
}
int Solve(int x)
{
if (x <= 1) return 1e9;
int res = 1e9;
for (int i=2;i*i<=x;++i)
{
if (x % i) continue;
res = min(res, Get(i));
while (x % i == 0) x /= i;
}
if (x > 1) res = min(res, Get(x));
return res;
}
int res = 1e9;
void work(int x)
{
res = min(res, Solve(x));
res = min(res, Solve(x + 1));
res = min(res, Solve(x - 1));
return;
}
signed main(void)
{
srand((unsigned)time(0));
n = read();
for (int i=1;i<=n;++i) a[i] = read();
int T = 10;
while (T --) work(a[1LL * rand() * rand() % n + 1]);
cout << res << endl;
return 0;
}