Codeforces 354C. Vasya and Beautiful Arrays【DP,暴力】

题目大意:

有一组数,你可以对每一个数做减法,减去的数不超过k,问最后你最大能得到的整个数组的GCD是多少

做法:

最后要求的是GCD(假设为g),那么也就是说,对于数组中的数a[i]来说,减去某个不大于k的值之后,就能被g整除;换句话说,a[i]%g<=k,只要满足该条件即可.

首先,由于只能对数进行减法操作,假设m为数组a中的最小值,ma为最大值;那么答案一定不会超过m,也就是说,答案的上界是m。
再观察上面得出的公式:a[i]%g<=k,如果g<=k+1,那么该公式是无论如何都满足的(因为a[i]%g,结果一定在0~g-1之间。)。也就是说,g的下界是k+1。

那么这样的话,是不是就说明我们可以暴力枚举k+1~m之间的数一个个的检查是否是正确答案呢?

答案为YES!
我们要求最大值,那当然是从大到小枚举,一旦找到答案,就输出,结束。

现在大体思路确定,解决剩下的小问题:我们应该怎么判断一个枚举的数是不是正确答案呢?
如果这个当前的g是正确答案,那么就是说,a数组中的每个数减去一个不大于k的值之后,变成了g的倍数(模g为0);也就是说,每个数都存在于[i*g,i*g+k](1<=i<=ma/g)这样的区间之间!那么我们只需要判断在这样的区间内的个数是否等于总的数的个数,如果相等,那就是正确答案,如果不等,那么继续枚举下一个答案。
那我们现在想想复杂度的问题,枚举过程中,最大循环次数应该是这样,n/1+n/2+n/3+......+n/n=N*logN(调和级数)复杂度也在允许范围内~

当然,当上界m小于下界k+1时,由于0~k+1的数一定是满足条件的,而答案又不能超过m,那毫无疑问,正确答案一定为m。

代码:
#include <iostream>
#include <cstdio>
#define N 1000010
using namespace std;
int a[N/3];
int sum[N*2];
int n,k,m,ma,ans;
bool running(int Ans)
{
    int limit=ma/Ans;
    int ret=0;
    for(int i=1;i<=limit;i++)
        ret+=(sum[i*Ans+k]-sum[i*Ans-1]);
    return ret==n;
}
int main()
{
    scanf("%d%d",&n,&k);
    m=11111111;
    for(int i=0;i<n;i++)
    {
        scanf("%d",a+i);
        sum[a[i]]++;
        m=min(m,a[i]);
        ma=max(ma,a[i]);
    }
    for(int i=m;i<=ma+k;i++){
        sum[i]+=sum[i-1];//求前缀和,判断某个区间内有多少个数
        //cout<<i<<": "<<sum[i]<<endl;
    }
    if(m<=k+1) {cout<<m<<endl;return 0;}
    for(ans=m;ans>=k+1;ans--)
    {
        if(running(ans))
        {
            cout<<ans<<endl;
            return 0;
        }
    }
    return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章