题意
给出n<=2e5个数,对每一个数x<=1e12进行一次操作可以使其+1,-1,求最小的操作次数,使得最后整个序列的gcd不为1,并且所有数都是正数
思路
整个序列的gcd不为1,也就是变换后序列每个数都有一个公共因子p,一个数x经过变换之后含因子p需要操作的次数为:
if(x<p) cnt=p-x%p
if(x>=p) cnt=min(p-x%p,x%p)
如果当gcd为2时,每个数最多只需要操作一次,那么操作次数上限就是n。
上限是n可以得出如果有某些数操作次数大于1,那么这些数的个数一定小于(lfloor frac{n}{2}
floor),如果大于,总次数就会大于n。
随机在序列中选一个数,它只需要操作<=1次的概率为(frac{1}{2}),随机选30个数,里面没有一个数是只操作<=1次的概率是(1-frac{1}{2}^{30}),概率值极小。所以可以随机取30个数,将每个数x,x-1,x+1的质因子求出来,最终序列的公共因子p就存在这些质因子中。
随机取序列中的30个数,可以使用洗牌函数shuffle,以当前时间作为随机数种子。
细节参考shuffle详解
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
typedef long long LL;
LL a[maxn];
set<LL> s;
int n;
inline void addPrime(LL x){
int sqr=sqrt(x);
for(int i=2;i<=sqr;++i)
if(x%i==0){
s.insert(i);
while (x%i==0)x/=i;
}
if(x>1)s.insert(x);
}
inline LL sol(LL p){
LL res=0;
for(int i=0;i<n;++i){
LL y=a[i];
if(a[i]<p)res+=(p-y%p);
else res+=min(p-y%p,y%p);
}
return res;
}
int main(){
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
unsigned seed=chrono::system_clock::now().time_since_epoch().count();
shuffle(a,a+n,default_random_engine(seed));
for(int i=0;i<n&&i<30;++i){
addPrime(a[i]);
addPrime(a[i]+1);
if(a[i]>1)addPrime(a[i]-1);
}
LL mx=maxn;
for(LL x : s){
mx=min(mx,sol(x));
}
cout<<mx<<endl;
return 0;
}