昨晚的一道CF题,orz没想到这样的前缀和
传送门:Arpa and a list of numbers
题意:
给一个序列,有两种操作
1、删除一个数,需要花费x
2、将某个数的值增加1,需要花费y现在要将这个序列变成空或者序列里的值gcd不等于1。
问至少要花费多少
思路:
做两个前缀和,一个用来记录有多少个数,一个用来记录到这个数的时候,和是多少。
那么我们当x >= a * y,a次增加操作的时候,使用2操作将它边到某个值,其他时候都使用1操作直接删除。然后枚举每一个因子,因为数就只有 106 ,我们可以很好的用筛法的原理,在接近O(n)的时间复杂度下,枚举每一个数以及他们的倍数。
对于每一个倍数j到这个倍数减去因数i的这个区间里,可以做2操作的数,我们可以直接通过前缀和得到有多少个数,且他们的数的和是多少,再减去他们原本值的和,乘上y,就是将这些数变成j的花费。剩下的数都进行1操作,把他们都删掉,乘上x即可。这里要特别注意的是。
题目给的是 106 但是他的可到倍数可能不止 106 ,可以枚举多一倍
#include <bits/stdc++.h>
#define MAXN 2000005
#define ll long long
using namespace std;
ll s1[MAXN];
ll s2[MAXN];
int main() {
ll n, x, y, a;
scanf("%lld %lld %lld", &n, &x, &y);
ll p = (ll)(x * 1.0 / y) + 1;
for (ll i = 1; i <= n; i++) {
scanf("%lld", &a);
s1[a]++;
s2[a] += a;
}
for (ll i = 1; i < MAXN; i++) {
s1[i] += s1[i - 1];
s2[i] += s2[i - 1];
}
ll ans = MAXN * n * y;
for (ll i = 2; i < MAXN; i++) {
ll tot = 0;
for (ll j = i; j < MAXN && tot < ans; j += i) {
ll k = max(j - i, j - p);
tot += ((s1[j] - s1[k]) * j - (s2[j] - s2[k])) * y + (s1[k] - s1[j - i]) * x;
}
ans = min(tot, ans);
}
printf("%lld\n", ans);
}