原题链接: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;
}