CF1305F Kuroni and the Punishment

Posted Jozky86

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF1305F Kuroni and the Punishment相关的知识,希望对你有一定的参考价值。

CF1305F Kuroni and the Punishment

题意:

给定 n 个数。每次可以选择将一个数 +1 或 -1 。求至少多少次操作使得整个序列都是正数且全部元素的 gcd>1 。
n < = 2 e 5 , a i < = 1 0 12 n<=2e5,a_{i}<=10^{12} n<=2e5,ai<=1012

题解:

首先不难想到,我们可以让所有数变成偶数,这样gcd为2,这样的话,每个数的操作次数小于等于1,这是答案的上界n,也就是最后的答案只会比n优,不可能比n劣
由上面这个结论我可以推出一个定理:在最终的方案中,指定存在至少一半的元素,他们最多被操作过一次
证明:如果存在一般元素被操作了超过一次,那么操作次数就是 ⌈ n + 1 2 ⌉ ∗ 2 > n \\lceil \\frac{n+1}{2}\\rceil*2>n 2n+12>n,这超过了答案上界
所以,如果我们随机选任意元素,这个元素至少有 1 2 \\frac{1}{2} 21的概率被最多操作一次。对于数x最多操作一次,可以得到x-1,x,x+1,也就是x最终有这个三个形态。因为gcd要>=2,说明x-1/x/x+1的某个因子就是我们要找的答案,那我们直接因子分解,对于每个因子w去强行判断如果w是gcd所需要的操作次数,然后取min
为什么这样是对的?
我们刚才说了,操作次数小于等于1的概率是>= 1 2 \\frac{1}{2} 21的,所以我们随机选取k次,那么错误的概率就是 1 2 k \\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);
        else
        ret+=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;
}

以上是关于CF1305F Kuroni and the Punishment的主要内容,如果未能解决你的问题,请参考以下文章