题意
给定 (n) 个正整数,一次操作可以将其中一个加减一,求最少操作数使得所有数都是正整数且 gcd 大于 1。(nleq 2 imes 10^5,a_ileq 10^{12})
题解
注意到答案不超过 (n)(gcd=2),考虑如果 gcd 不是小质数(的倍数),那么需要加减 2 的数不超过一半。我们多次随机两个数,求其(加减一/不变)后的 gcd,并钦定 gcd 为其约数,计算代价。每次随机有不少于 (dfrac{1}{2}) 的概率正确。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll getint(){ ll x;scanf("%lld",&x);return x; }
const int N=2e5+10,K=300,T=1e4;
mt19937 rnd;
int pri[K],cnt; bool boo[K+10];
int n;
ll a[N];
map<ll,int>mp;
ll gcd(ll x,ll y){
return x?gcd(y%x,x):y;
}
pair<ll,int>q[N];
int main(){
freopen("gcd.in","r",stdin);
n=getint();
for(int i=1;i<=n;i++)a[i]=getint();
for(int i=2;i<=K;i++){
if(!boo[i])pri[cnt++]=i;
for(int j=0;j<cnt&&i*pri[j]<=K;j++){
boo[i*pri[j]]=0;
if(i%pri[j]==0)break;
}
}
ll ans=n;
for(int i=0;i<cnt;i++){
int p=pri[i];
ll v=0;
for(int j=1;j<=n;j++){
if(a[j]<p)v+=p-a[j];
else{
ll t=a[j]%p;
v+=min(t,p-t);
}
}
ans=min(ans,v);
}
for(int _=0;_<10;_++){
int x=rnd()%n+1,y=rnd()%n+1;
for(int d:{-2,-1,0,1,2}){
if(min(a[x],a[y])+d<=0)continue;
ll g=gcd(a[x]+d,a[y]+d);
for(int j=0;j<cnt;j++)while(g%pri[j]==0)g/=pri[j];
mp[g]++;
for(int p=2;p*1ll*p<=g;p++){
if(g%p==0){
ll v=0;
for(int j=1;j<=n;j++){
if(a[j]<p)v+=p-a[j];
else{
ll t=a[j]%p;
v+=min(t,p-t);
}
}
ans=min(ans,v);
}
while(g%p==0)g/=p;
}
if(g!=1){
ll p=g;
ll v=0;
for(int j=1;j<=n;j++){
if(a[j]<p)v+=p-a[j];
else{
ll t=a[j]%p;
v+=min(t,p-t);
}
}
ans=min(ans,v);
}
}
}
cout<<ans;
}
花絮:搬题人的数据非常水,输出 (n-1) 就能过题。