题目链接:F. Kuroni and the Punishment
题目大意:给定一个正整数序列(a_1,a_2,cdots,a_n),你每一次操作可以使每一个数(+1)或(-1),但必须保证操作后的数仍为正整数,问最少用多少次操作才能使它们的最大公约数不为(1)。
题解:这样的题拿到之后第一反应就是瞎搞(然而博主太菜了赛时瞎搞了16发都没对),肯定是枚举素因子然后计算答案了,我们先假设我们已经知道了素因子是什么,假设其为(p),所以有一个很明显的贪心策略,就是每一个数只会变成与它相邻的两个是(p)的倍数的正整数,所以我们就可以得到一个(O(n))的贪心策略。先把(2)和(3)跑一遍(因为这两个最容易成为答案),然后就是随机了,随机抽取序列中的一个数,令其为(x),然后把(x)分解质因数,对它的每一个质因数都跑一遍,然后再这么操作(x-1)和(x+1),这样的话算法错误的概率是(frac{1}{2}),然后重新随机。这样随机 20 次之后,程序错误的概率就是(2^{-20}),当然了,赛场上没有必要这么算,直接卡时间就可以了。
下面是代码(随机函数最好用 mt19937 ,可能是博主太菜了用 rand 结果WA7):
#include <ctime>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
template<typename Elem>
void read(Elem &a){
a=0;
char c=getchar();
while(c<'0'||c>'9'){
c=getchar();
}
while(c>='0'&&c<='9'){
a=(a<<1)+(a<<3)+(c^48);
c=getchar();
}
}
const int Maxn=200000;
typedef long long ll;
int n;
ll a[Maxn+5];
ll p[Maxn+5];
int p_len;
void div(ll x){
p_len=0;
for(ll i=2;i*i<=x;i++){
if(x%i==0){
p[++p_len]=i;
while(x%i==0){
x/=i;
}
}
}
if(x>1){
p[++p_len]=x;
}
}
ll work(ll p){
ll ans=0;
for(int i=1;i<=n;i++){
if(a[i]<p){
ans+=p-a[i];
if(ans>=n){
break;
}
continue;
}
ll fron=a[i]/p;
ll bac=(fron+1)*p;
fron*=p;
ans+=min(a[i]-fron,bac-a[i]);
if(ans>=n){
break;
}
}
return ans;
}
int main(){
clock_t begin,end;
begin=clock();
mt19937 rnd(time(NULL));
read(n);
for(int i=1;i<=n;i++){
read(a[i]);
}
ll ans=n;
ll num=0;
for(int i=1;i<=n;i++){
if(a[i]&1){
num++;
}
}
ans=min(ans,num);
num=0;
for(int i=1;i<=n;i++){
if(a[i]==1){
num+=2;
}
else if(a[i]%3!=0){
num++;
}
}
ans=min(ans,num);
while(1){
int x=rnd()%n+1;
div(a[x]);
for(int i=1;i<=p_len;i++){
ans=min(ans,work(p[i]));
}
div(a[x]-1);
for(int i=1;i<=p_len;i++){
ans=min(ans,work(p[i]));
}
div(a[x]+1);
for(int i=1;i<=p_len;i++){
ans=min(ans,work(p[i]));
}
end=clock();
if((end-begin)*1.0/CLOCKS_PER_SEC>2.0){
break;
}
}
cout<<ans<<endl;
return 0;
}