您的位置:首页 > 其它

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

2017-09-05 17:59 471 查看
题意:给出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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息