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