CF1305F Kuroni and the Punishment
题意:
给定 n 个数。每次可以选择将一个数 +1 或 -1 。求至少多少次操作使得整个序列都是正数且全部元素的 gcd>1 。
n<=2e5,ai<=1012n<=2e5,a_{i}<=10^{12}n<=2e5,ai<=1012
题解:
首先不难想到,我们可以让所有数变成偶数,这样gcd为2,这样的话,每个数的操作次数小于等于1,这是答案的上界n,也就是最后的答案只会比n优,不可能比n劣
由上面这个结论我可以推出一个定理:在最终的方案中,指定存在至少一半的元素,他们最多被操作过一次
证明:如果存在一般元素被操作了超过一次,那么操作次数就是⌈n+12⌉∗2>n\lceil \frac{n+1}{2}\rceil*2>n⌈2n+1⌉∗2>n,这超过了答案上界
所以,如果我们随机选任意元素,这个元素至少有12\frac{1}{2}21的概率被最多操作一次。对于数x最多操作一次,可以得到x-1,x,x+1,也就是x最终有这个三个形态。因为gcd要>=2,说明x-1/x/x+1的某个因子就是我们要找的答案,那我们直接因子分解,对于每个因子w去强行判断如果w是gcd所需要的操作次数,然后取min
为什么这样是对的?
我们刚才说了,操作次数小于等于1的概率是>=12\frac{1}{2}21的,所以我们随机选取k次,那么错误的概率就是12k\frac{1}{2^k}2k1
当k>=30时一般不会出错
还有随机不要用rand(),详细原因详见如何正确地生成一个随机数
代码:
#include<bits/stdc++.h>
using namespace std;
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
typedef long long ll;
ll a[200005];
int n;
ll cal(ll x)
{ll ret=0;for (int i=0;i<n;i++){ll tmp=a[i]%x;if (a[i]!=tmp)ret+=min(tmp,x-tmp);elseret+=x-tmp;}return ret;
}ll fac(ll x)
{ll ret=1e18;ll en=sqrt(x+1ll);for(ll i=2;i<=en;i++){if (x%i==0){ret=min(ret,cal(i));while(x%i==0)x/=i;if (x==1)break;}}if (x>1)ret=min(ret,cal(x));return ret;
}
int main()
{ srand(time(0));cin>>n;for(int i=0;i<n;i++)scanf("%I64d",&a[i]);int T=50;ll ans=1e18;while (T--){ll pos=rnd()%n;if (a[pos]>2)ans=min(ans,fac(a[pos]-1ll));ans=min(ans,fac(a[pos]));ans=min(ans,fac(a[pos]+1ll));}cout<<ans;
}