思路:首先,答案的gcd肯定是素数,素数的倍数是没有意义的, 素数的倍数可以, 那这个素数肯定可以,其次,你知道了答案gcd, 整个数列的最小费用就知道了, 直接比较删除跟取余之后+1的最小值就好了(但这题利用不了这个性质),这题的做法就是, 枚举素数,然后分段, 对g*i到g*(i+1)的花费进行统一计算,每次+=g,复杂度很有保证的,而且这一段区间里的性质(gcd)是一样的,所以每一段很好处理,前缀一下就是O1的,这里mengxiang000讲的比较好
我们知道,Gcd的结果是素数是最好的,所以我们首先筛出素数,1e6内的素数个数约为8w(8e4)个。
然后考虑对应数字是删除还是增添。很显然我们这里可以Dp去做,但是时间复杂度很爆炸,所以考虑X和Y两种操作之间的关系。
①不难想到,如果X<Y的话,我们就不用进行Y操作了。
②所以我们设定move=X/Y表示我们最多对一个数进行增添数字的次数,如果超过了这个次数,我们不如将其删除来的划算。
接下来考虑如何计算结果。首先我们肯定要O(8e4)去枚举答案,接下来考虑,既然我们已经知道了move这个信息,那么对于当前枚举的素数p,我们有一个区间【p,2p】的话,我们肯定能够通过move这个信息找到区间的一个分界点,使得【p~分界点】的数我们都将其删除,使得【分界点~2p】的数字都增添变成2p.
那么这里我们只要枚举出来一个素数p,然后每一次计算之后将区间移动p个长度即可。整体时间复杂度O(Len*LogLen)这里Len表示素数的个数。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
const ll INF = 1e18;
const int maxn = 2e6 + 5;
int prime[maxn], cnt[maxn], n, a[maxn], pnum, phi[maxn];
ll sum[maxn], x, y;
bool book[maxn];
void init()
{
for(int i = 2; i < maxn; i++)
{
if(!book[i])
{
prime[pnum++] = i;
phi[i] = i - 1;
}
for(int j = 0; j < pnum && prime[j]*i < maxn; j++)
{
book[prime[j]*i] = 1;
if(i%prime[j] == 0)
{
phi[prime[j]*i] = phi[i]*prime[j];
break;
}
else
phi[prime[j]*i] = phi[i]*(prime[j]-1);
}
}
}
int main()
{
pnum = 0;
init();
scanf("%d%lld%lld", &n, &x, &y);
int tmp;
for(int i = 1; i <= n; i++)
{
scanf("%d", &tmp);
cnt[tmp]++;
sum[tmp] += tmp;
}
// cout << 1 << endl;
for(int i = 1; i < maxn; i++)
cnt[i] += cnt[i-1], sum[i] += sum[i-1];
int num = x/y;
ll ans = INF;
for(int i = 0; i < pnum; i++)
{
ll tmp = 0;
for(int j = prime[i]; j < maxn; j += prime[i])
{
ll limit = max(j-prime[i]+1, j-num);
ll dele = cnt[limit-1] - cnt[j-prime[i]];
ll add = sum[j] - sum[limit-1];
ll addcnt = cnt[j] - cnt[limit-1];
tmp += dele*x;
tmp += y*(j*addcnt - add);
}
ans = min(ans, tmp);
}
printf("%lld\n", ans);
return 0;
}