codeforces 850B Round #432 Div2D & Div1B:数论+计数

来源:互联网 发布:灭绝师太扫矿软件 编辑:程序博客网 时间:2024/09/21 06:41

题意:给出n(<=5e5)个数字,每个数字在[ 1 , 1e6 ]范围内,定义bad为:1、序列非空;2、所有数字的GCD=1.现在有两种操作:1、删除某个数字,花费是x。2、给某个数字+1,花费为y,一个数字可以被加很多次。请求出让这个序列变成good(非bad)的最小花费。

题解:又是一个计数的问题。显然,我们希望得到GCD!=1,那么我们只需要枚举每个质数,让他作为GCD算最小花费,然后取最优即可。


一个显然的思路是,在1e6范围内进行素数筛,枚举素数,然后枚举每个数字,有两种方案:删掉(花费是x)、把他续了(花费是((ai+GCD-1)/GCD*GCD-ai)*y)。但是估计一下复杂度发现,复杂度是 素数个数*n的。但是1e6范围内有好几万个素数,然后愉快的TLE.


那么既然这个思路TLE了。我们就不能红果果的去算总花费,需要用一定的计数技巧。常用的方法是:每个数字遍历一遍来计数很慢,那如果可以一段区间一起计数就可以很快了。那么我们观察,当枚举一个素数GCD之后,每个数字有两种方案,删掉(x),续了(k*y),而且k*GCD这样的数字代价为0,于是我们考虑一个区间里边的数字[ k*GCD+1 , (k+1)*GCD-1 ],这个区间里边的每个数字都会得到一个花费。显然数字小的,删掉只需要x,如果续的话需要很多次y,那么小的数字删掉比较优,大的数字续了比较优。中间会有一个临界点,这个临界点就是K=x/y,某个数字a,最小的比他大的GCD的倍数是P,那么如果P-a<=k的话,把他续了比把他删了更优,相反如果P-a>k那么把他删了更优,于是我们可以用这种方法对落在长度为GCD-1区间内的数字一次性处理完毕。然后总区间长度是1e6,所以复杂度是1e6*(1/p1 +1/p2 +……+1/px)复杂度小于mlogm(m=1e6).


细节:对于区间前半段,我们直到删掉他们更优,于是我们只需要知道 ,某段区间内的数字个数有几个,这个可以用一个前缀和得到。

对于去见后半段,我们需要知道这些数字和(k+1)*GCD的差值的和,也就是∑((k+1)*GCD-ai)*y。假设总共有d个ai。那么可以化为(d*(k+1)*GCD-∑ai)*y。这个个数d可以通过前边那个前缀和得到。后边这个∑ai可以通过另外一个前缀和得到。


Code:

#include<bits/stdc++.h>using namespace std;const int MAX = 5e5+100;const int MAXX = 2e6+100;int prime[MAXX];int a[MAX];int sum1[MAXX];long long sum2[MAXX];bool vis[MAXX];int tot;int n,x,y;long long ans = 0x3f3f3f3f3f3f3f3fLL;void input(){scanf("%d%d%d",&n,&x,&y);for (int i=0;i<n;i++){scanf("%d",a+i);sum1[a[i]]++;sum2[a[i]]+=a[i];}}void init(){for (int i=1;i<=2e6;i++){sum1[i]+=sum1[i-1];sum2[i]+=sum2[i-1];}for (int i=2;i<=1e6;i++){if (!vis[i]){prime[tot++] = i;vis[i] = true;}for (int j=0;j<tot;j++){int temp = i*prime[j];if (temp>1e6){break;}vis[temp] = true;}}}void solve(){for (int i=0;i<tot;i++){long long res =0;int P = prime[i];int k = min(x/y,P-1);for (int j=0;j<=1e6;j+=P){res+=1LL*(sum1[j+P-k-1]-sum1[j])*x;res+=(1LL*(sum1[j+P-1]-sum1[j+P-k-1])*(j+P)-sum2[j+P-1]+sum2[j+P-k-1])*y;}ans = min(ans,res);}printf("%I64d\n",ans);}int main(){input();init();solve();return 0;}


阅读全文
0 0
原创粉丝点击