codeforces 580B Arpa and a list of numbers 前缀和+思维+分块 (调和级数)

来源:互联网 发布:开票软件商品编码 编辑:程序博客网 时间:2024/05/18 23:54

题目链接


题意:

给出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;}


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