Codeforces Codeforces Round #432 (Div. 2 D ) Arpa and a list of numbers 爆搜+剪枝

原题链接:http://codeforces.com/contest/851/problem/D

大致题意:给出一个序列,有两种操作,第一种操作是删除某个元素,代价为x,第二种操作是令某个元素自增1,代价为y。第二种操作可以对某个元素重复多次。

问通过这两种操作使得序列的所有元素的gcd不为1的最小代价是多少。

显然,最终的序列所有元素的gcd一定是某个素数q的倍数,可以枚举这个素数q然后对每个元素a[i]的单独的代价min( x , y*t),其中t代表第一个不小于a[i]的q的倍数与a[i]之差。

这样子直接暴力时间复杂度大概是O( n * m),m是素数的个数,大致上也是10^5。

但是其实有很多素数是肯定更新不了答案的。

可以先统计num[i],即序列中有个多少数是第i个素数的倍数,则第i个素数统计出的答案一定不会小于

g[i]= ( n - num[ i ] ) * ( min ( x , y ) ) ,所以可以先用g[i]和当前ans进行比较,如果g[i]>ans则不必再搜索这个素数了。

代码:(CF实测1000ms左右)

#include <bits/stdc++.h>
using namespace std;
inline void read(int &x){
    char ch;
    bool flag=false;
    for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
    for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
    x=flag?-x:x;
}

inline void read(long long &x){
    char ch;
    bool flag=false;
    for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
    for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
    x=flag?-x:x;
}

int const maxn=2000000;
int n;
long long a[maxn];
long long  x,y;
long long su[maxn],tot;
long long num[maxn];
bool vis[ maxn ];

void prepare(){
vis[1]=1;
for (int i=2;i< maxn; i++)
    {
        if ( vis[i]==0 )
            su[++tot]=i;
        for (int j=1;j<=tot;j++)
            {
                if( 1ll*i*su[j]>maxn )
                    break;
                vis[ i*su[j] ]=1;
                if ( i %su[j] ==0 )
                    break;
            }
    }
}


long long ans;



long long doit(long long now){
long long sum=0;
for (int i=1;i<=n;i++)
{
    long long t=a[i]%now;
    if (t!=0)
        t=now-t;
    sum=sum+min( x , t*y );
    if (sum>=ans)
        return x*n;
}
return sum;
}


int get_num(int x){
int l=1;
int r=tot;
int tmp=r;
while (l<=r)
    {
        int mid=(l+r)/2;
        if( su[mid]<=x)
            {
                l=mid+1;
                tmp=mid;
            }
        else
            r=mid-1;
    }
return tmp;
}

int main(){
    //freopen("a.in","r",stdin);
    prepare();
    read(n); read(x); read(y);
    long long maxx=0;
    ans=1ll*x*n;
    for (int i=1;i<=n;i++)
    {
        read(a[i]);
        maxx=max(maxx,a[i]);
    }
    ans=min( doit( su[1] ) ,ans);
    for (int i=1;i<=n;i++)
    {
        int tmp=a[i];
        for (int j=1;j<=tot;j++)
            {
                if ( su[j] > sqrt( tmp ) )
                    break;
                if ( tmp %su[j]==0)
                {
                    num[j]++;
                    while ( tmp %su[j]==0)
                        tmp=tmp/su[j];
                }
            }
        if(  !vis[ tmp ])
            num[ get_num( tmp) ]++;
    }

    for (int i=1;i<=tot;i++)
        if ( num[i] >= num[1] )
        {
            if ( ( n-num[i] ) *min(x,y)< ans )
                ans=min( doit( su[i] ) ,ans);
            if ( su[i] >= maxx)
                break;
        }
    /*
    for (int i=1;i<=tot;i++)
    {
        ans=min( doit( su[i] ) ,ans);
        if (su[i]>maxx)
            break;
    }
    */
    printf("%I64d\n",ans);
    return 0;
}

后来上午和同学交流了一下,才发现我的做法并不够优秀。其实正解是在对每个素数求解的时候优化了求答案的过程,每次查询k*q到(k+1)*q之间的数之和,以及元素的数量,因为如果(k+1)*q-x>y/x的话肯定是通过第二种操作来使得gcd不为q的倍数,否则就只能删除该元素。这个过程由于在值为[ max(   (k+1)*q-y/x  , k*q ) , (k+1)*q ]的区间内时,都是采用第二种操作,否则是第一种操作。因此可以预处理出前缀和 以及 元素值的前缀数量(即值为从0到x的元素的数量),然后就可以快速求出答案了。

时间复杂度是O(n*log(n) )左右,CF实测是260ms左右

代码:


#include <bits/stdc++.h>
using namespace std;
inline void read(int &x){
    char ch;
    bool flag=false;
    for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
    for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
    x=flag?-x:x;
}

inline void read(long long &x){
    char ch;
    bool flag=false;
    for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
    for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
    x=flag?-x:x;
}

int const maxn=2000000;
int n;
long long a[maxn];
long long  x,y;
long long su[maxn],tot;
long long num[maxn];
bool vis[ maxn ];
long long sum[maxn];
long long total[maxn];

void prepare(){
vis[1]=1;
for (int i=2;i< maxn; i++)
    {
        if ( vis[i]==0 )
            su[++tot]=i;
        for (int j=1;j<=tot;j++)
            {
                if( 1ll*i*su[j]>=maxn )
                    break;
                vis[ i*su[j] ]=1;
                if ( i %su[j] ==0 )
                    break;
            }
    }
}


long long ans;

long long doit(long long now){
long long tmp=0;
long long tmpy=0;
long long tmpx=0;

for (long long i=0;i<a[n];i+=now)
    {
        long long r,mid;
        r = min ( i+now-1 , a[n]);
        mid = i+now - min( now , x/y+1 );
        if (mid>r)
            mid=r;
        tmpy=tmpy+( (i+now)*( total[ r ]-total[ mid ] ) - (   sum[ r ]-sum[ mid  ] ) );
        if ( tmpy > ans/y)
            return ans;
        tmpx=tmpx+( total[  mid ] - total[ i ] );
    }
if ( ans/y >= tmpy )
    tmp=tmp+tmpy*y;
else
    return ans;
if ( (ans-tmp)/x >= tmpx )
    tmp=tmp+tmpx*x;
else
    return ans;
return tmp;
}

int get_num(int x){
int l=1;
int r=tot;
int tmp=r;
while (l<=r)
    {
        int mid=(l+r)/2;
        if( su[mid]<=x)
            {
                l=mid+1;
                tmp=mid;
            }
        else
            r=mid-1;
    }
return tmp;
}

int main(){
    //freopen("a.in","r",stdin);
    prepare();

    read(n); read(x); read(y);
    long long maxx=0;
    ans=1ll*x*n;
    for (int i=1;i<=n;i++)
    {
        read(a[i]);
        maxx=max(maxx,a[i]);
        total[ a[i] ] ++;
    }
    sort(a+1,a+n+1);
    for (int i=a[1];i<=a[n];i++)
    {
        sum[i]=sum[i-1]+total[i]*i;
        total[i]=total[i-1]+total[i];
    }

    ans=min( doit( su[1] ) ,ans);

    for (int i=1;i<=tot;i++)
        {
            ans=min( doit( su[i] ) ,ans);
            if ( su[i] >= maxx)
                break;
        }
    printf("%I64d\n",ans);
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值