1. 程式人生 > >【51Nod1244】莫比烏斯函式之和-杜教篩+雜湊表

【51Nod1244】莫比烏斯函式之和-杜教篩+雜湊表

測試地址:莫比烏斯函式之和
做法:這題需要使用杜教篩+雜湊表。
以下方括號[]表示若括號內式子為真,則該符號值為1,否則值為0
首先我們設莫比烏斯函式前n項的和為f(n),即f(n)=ni=1μ(i),那麼我們只需求f(b)f(a1)。使用d|nμ(d)=[n=1]來作進一步推導,我們可以先找到函式e(n)=[n=1]的字首和,顯然ni=1e(i)=1,那麼ni=1d|iμ(d)=1,所以:

i=1nd=1niμ(d)=i=1nf(ni)=1
f(n)那項提出來,得到:
f(n)=1i=2nf(ni)
因為ni只有O(N)種取值,所以分塊計算,需要用到的f
值再遞迴計算,可以證明這個演算法的時間複雜度是O(N34)
然而我們還可以優化一下:我們發現一些比較小的f(n)值可以預處理出來,最好預處理出前N23項,據說這樣時間可以優化到O(N23)。而且在計算f的時候,發現有些函式值被重複計算了,那麼我們可以寫個雜湊表判個重就可以優化掉這些時間了。
以下是本人程式碼(25個測試點TLE了1個點,有待繼續學習):
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long #define limit 1000000 #define mod 7500000 using namespace std; ll a,b,h[mod+5]={0}; int miu[limit+5],sum[limit+5],f[mod+5]; bool prime[limit+5]={0}; int hash(ll x) { int s=x%mod; while(h[s]&&h[s]!=x) s=(s+1)%mod; return s; } void calc_miu(ll x) { for(int i=1;i<=x;i++) miu[i]=1
; for(ll i=2;i<=x;i++) if (!prime[i]) { for(ll j=1;j*i<=x;j++) { prime[i*j]=1; if (!(j%i)) miu[i*j]=0; miu[i*j]*=-1; } } sum[0]=0; for(int i=1;i<=x;i++) sum[i]=sum[i-1]+miu[i]; } ll count(ll x) { int pos=hash(x); if (x<=limit) return (ll)sum[x]; if (h[pos]==x) return f[pos]; ll s=0,i=2,next; while(i<=x) { next=x/(x/i); s+=(next-i+1)*count(x/i); i=next+1; } h[pos]=x,f[pos]=1-s; return 1-s; } int main() { calc_miu(limit); scanf("%lld%lld",&a,&b); printf("%lld",count(b)-count(a-1)); return 0; }