1. 程式人生 > >codeforces 850B Round #432 Div2D & Div1B:數論+計數

codeforces 850B Round #432 Div2D & Div1B:數論+計數

題意:給出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;
}