题意:
给出N个数,删除一个数的花费是X,改变一个数从num变成num+1的花费是Y,问将整个序列的Gcd改成不是1的最小花费。
思路:
首先考虑枚举gcd,我们知道gcd为素数肯定最优了,因为你枚举质数的倍数为gcd的话,排着枚举肯定会先枚举到质数,不是最优.
1e6以内的素数大约8e4个,而元素最多有5e5.这样还是会TLE。
我们知道,基于贪心的思想肯定是我们枚举一个gcd,然后对于一些数是删掉他最优一些是直接每次+1最优.
1.如果x<y 那么不需要每次+1,不如直接删除来的快。
2.x>=y 考虑一个比例 rate = x/y,这个也就是我们最多进行的+1操作次数,超过这个次数也不如直接删除来的更优.
那么假设我们现在枚举的gcd为质数p,那么对于一个数范围的区间 (k*p,(k+1)*p) ,在这个区间内肯定存在某个临界点,该点左面所有点全部删除,该点右面所有的值一直加到 (k+1)*p,来使得答案最优.
由上面我们所说的,rate为最多的加1的操作次数,那么给定区间我们可以计算出临界点为:
fen = ((k+1)*prime[i]-rate-1,k*prime[i])。
需要定义几个数组:
num[i] 小于等于i的数的个数
sum[i] 小于等于i的数的和.
复杂度:
枚举每个gcd,然后每次按照区间来处理, 对于每一个gcd最多有N/gcd 个区间. N为数的最大值
所以复杂度近似一个调和级数 NlogN
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e6+10;
typedef long long ll;
const ll inf = 1e15;
int prime[maxn] = {0};
bool vis[maxn] ={1,1};
ll sum[maxn];
int num[maxn];
ll n,x,y,cnt=0;
void init()
{
for(int i = 2;i < maxn ;i++)
{
if(!vis[i])
{
prime[cnt++] = i;
}
for(int j=0;j < cnt && (ll)i*prime[j] < maxn ;j++)
{
vis[i*prime[j]]=1;
if(i % prime[j] == 0)
break;
}
}
}
int main()
{
init();
while(cin>>n>>x>>y)
{
memset(sum,0,sizeof sum);
memset(num,0,sizeof num);
int a;
int ma = 0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a);
num[a]++;
sum[a] += a;
ma =max(a,ma);
}
for(int i=1;i <= 2*ma;i++)
{
num[i] += num[i-1];
sum[i] += sum[i-1];
}
ll rate = x/y;
ll ans = inf;
for(int i = 0;i < cnt && prime[i-1] <= ma;i++)
{
ll res = 0;
for(int j=0;j*prime[i] <= ma;j++)
{
ll fen = max((ll)j*prime[i],(ll)(j+1)*prime[i]-rate-1);
ll delnum = num[fen] - num[j*prime[i]];//计算有多少个数要被删除
ll addsum = sum[(j+1)*prime[i]]-sum[fen];//计算要加1的数的和.
ll addnum = num[(j+1)*prime[i]] - num[fen];//计算有多少个数要+1.
ll cons = addnum*(j+1)*prime[i] - addsum;//加完以后的和-没加之前的和,为需要加几次
res += cons*y + delnum*x;//计算.
if(res > ans)
break;
}
ans = min (ans,res);
}
printf("%lld\n",ans);
}
return 0;
}