min25篩詳解+如何推式子+杜教篩

Part.1  min25篩

前情提要:應用特別廣泛,代碼難度一般,但理解難度較大

 

若f(i)爲積性函數

\sum_{i=1}^nf(i)

主要思路就是將積性函數分爲三個部分來求和,當 i 是質數,當 i 是合數,還有i等於1

它的思想類似DP

首先需要了解一下埃氏篩法(圈一個質數,劃去它的倍數)

我們先不考慮1

先來考慮如何表示一個埃氏篩的狀態

我們可以從篩子的範圍與所圈質數的個數來表示一個篩子

設g[n][k]表示篩子的範圍爲2~n,用前k個質數來篩,最後沒有被叉掉的數的 f 值之和

怎麼轉移?

考慮埃氏篩的過程,選定一個質數後,直接從它的平方開始篩

所以當Pk*Pk>n時,g[n][k]=g[n][k-1] (Pk是第k個質數)

否則我們就一定會再叉掉一些數,減掉一部分f值

g[n][k]=g[n][k-1]-f(P_k)*(g[\left \lfloor \frac{n}{P_k} \right \rfloor][k-1]-g[P_k-1][k-1])

這裏利用了f是積性函數的條件

f(P_k)*g[\left \lfloor \frac{n}{P_k} \right \rfloor][k-1]

理解:用前k-1個質數來篩,把沒篩掉的數,作爲Pk的倍數(這個倍數一定會前k-1個質數互質)(當然,這個倍數*Pk應該小於n)來確定Pk需要篩哪些點。由於前k-1個質數也是剩下的數,所以還要加上f(P_k)*g[P_k-1][k-1](就是f(P_K)*\sum_{i=1}^{k-1}f(P_i),即Pk與前k-1個質數的組合)

注意:由於我們是要求i爲質數是f值之和,所以我們一開始應該把所有數都當成質數

(具體實現後面再講)

我們已經完成了第一步

 

第二步就是求出合數部分的答案

其實我們已經可以直接求所有數(除1以外)的答案了

設s[n][k]表示篩的範圍爲2~n,用了前k-1個質數來篩,剩下的數的 f 值之和(除去前k-1個質數的f值)

(感覺跟g的定義一模一樣?其實不是,s求的是真正的答案,而g是把所有的數當成質數來算的答案)

(而且g是去掉了Pk篩掉的合數,s包含了Pk篩掉的合數,但又要去掉前k-1個質數的答案)

同理Pk*Pk>n時,s[n][k]=s[n][k-1]-f(P_{k-1})

我們明顯知道當P_{k+1}*P_{k+1}>nP_k*P_k<=n時(篩的臨界點),再讓P_{k+1}來篩已經沒有意義了,即剩下的都是質數了

所以此時的s[n][k]就是g[n][k]-f(P1)-f(P2)-...-f(P_{k-1})

爲了方便,我們把這個臨界點的k值即爲 |P|

於是我們有轉移:

s[n][k]=g[n][|P|]-\sum_{i=1}^{k-1}f(P_i)+\sum_{i=k}^{P_i^2<=n}\sum_{j=1}^{P_i^{j+1}<=n} (f(P_i^j)*s[\left \lfloor \frac{n}{P_i^j} \right \rfloor][i+1]+f(P_i^{j+1}))

我們現在相當於在執行篩質數的逆過程,即用質數組合出合數的過程

我們可以寫出一個等價的狀態定義:s[n][k]表示用第k~|P|的質數,組合出的數(包括質數本身)的 f 值之和

注意:這裏新組合的數指的是將最小質因子大於等於Pk的數

理解:

g[n][|P|]-\sum_{i=1}^{k-1}f(P_i)在單獨計算質數的答案

f(P_i^j)*s[\left \lfloor \frac{n}{P_i^j} \right \rfloor][i+1]

枚舉當前質數Pi,以及它的冪次j,作爲最小質因子,用與前k個質數互質的數來作爲他的倍數,

求出曾經被它叉掉的(即以它作爲最小質因子,新組合出來的)數的 f 值之和

由於這樣只能求它與其他質數組合出的合數的答案

所以我們還要加上f(P_i^{j+1}),讓他自己和自己也可以組合出合數

最後加上f(1)的答案就好啦

 

時間複雜度?O(n^{1-\epsilon })(差不多線性),但是在n比較小的情況下是O(\frac{n^{\frac{3}{4}}}{logn})

 

具體實現:

拿一道例題來講

 

最小公倍數計數

定義F(n)表示最小公倍數爲n的二元組的數量。

即:如果存在兩個數(二元組)X,Y(X <= Y),它們的最小公倍數爲N,則F(n)的計數加1。

例如:F(6) = 5,因爲(2,3)(1,6)(2,6)(3,6)(6,6)的最小公倍數等於6。

給出一個區間a,b,求最小公倍數在這個區間的不同二元組的數量。

例如:a = 4,b = 6。符合條件的二元組包括:(1,4)(2,4)(4,4)(1,5)(5,5)(2,3)(1,6)(2,6)(3,6)(6,6),共10組不同的組合。

Input

輸入數據包括2個數:a, b,中間用空格分隔(1 <= a <= b <= 10^11)。

Output

輸出最小公倍數在這個區間的不同二元組的數量。

Sample Input

4 6

Sample Output

10

 

 

很明顯,F是一個積性函數

差分一下,求F的前綴和

轉換一下,我們先來計算有序對的個數

爲了方便,我們把p都當成是質數

F(p)=3,因爲只可能有(1,p)(p,1)(p,p)三種組合

F(p^c)=2c+1,因爲我們必須將其中一個數固定爲p^c纔可以讓lcm取到p^c,剩下一個數有c+1種取值(1,p,p^2,p^3....p^c)

但由於兩個數同時取p^c的情況被重複計算了,所以要減1

F(ab),當a,b互質時,顯然有F(ab)=F(a)*F(b),即分開組合a與b

於是我們就把問題轉換爲了求積性函數F的前綴和

先放上套路式:

g[n][k]=g[n][k-1]-f(P_k)*(g[\left \lfloor \frac{n}{P_k} \right \rfloor][k-1]-\sum_{i=1}^{k-1}f(P_i))

s[n][k]=g[n][|P|]-\sum_{i=1}^{k-1}f(P_i)+\sum_{i=k}^{P_i^2<=n}\sum_{j=1}^{P_i^{j+1}<=n} f(P_i^j)*s[\left \lfloor \frac{n}{P_i^j} \right \rfloor][i+1]+f(P_i^{j+1})

首先,我們發現n的取值只有\sqrt{n}種(因爲每次只調用了\left \lfloor \frac{n}{x} \right \rfloor作爲下標(\left \lfloor \frac{\left \lfloor \frac{n}{x} \right \rfloor}{y} \right \rfloor=\left \lfloor \frac{n}{xy} \right \rfloor))

於是可以把這些位置預處理出來,n*k --> sqrt(n)*k

for(i=1;i<=n;i=j+1){
    	j=n/(n/i);
    	a[++m]=n/i;
    	if(a[m]<=T)id1[a[m]]=m;//小於根號n
        else id2[n/a[m]]=m;
}

由於當前狀態只與k-1有關,g[n]可以原地轉移,省去k,sqrt(n)*k --> sqrt(n)

 

然後把g[n]在k=0時的值(相當於把所有數當成質數,求前綴和)預處理出來

for(i=1;i<=n;i=j+1){
        j=n/(n/i);
        a[++m]=n/i;
        if(a[m]<=T)id1[a[m]]=m;
        else id2[n/a[m]]=m;
        g[m]=3ll*(a[m]-1);
}

計算出k=|P|時的g數組

#define id(i) (((i)<=T)?id1[i]:id2[n/(i)])
for(j=1;j<=tot;j++)
	for(i=1;i<=m&&prime[j]*prime[j]<=a[i];i++)
		g[i]-=g[id(a[i]/prime[j])]-3ll*(j-1);

此時a[i]存的是位置 i 的實際取值(a是降序),id(x)求的是實際取值x對應的位置

先要線性篩一下質數,篩到sqrt(n)即可

枚舉當前質數prime[j],再枚舉它可以讓哪些取值 a[i] 發生轉移

由於a[i]越小越不容易轉移,所以當第一個a值不滿足時,後面的都已經不滿足了,也不用轉移了

(原地轉移:不轉移等價於g[n][k]=g[n][k-1])

3ll*(j-1) 就是在計算\sum_{i=1}^{j-1}f(P_i)

 

s[n][k]部分的計算就比較好理解了(遞歸計算)(直接套式子)

s[n][k]=g[n][|P|]-\sum_{i=1}^{k-1}f(P_i)+\sum_{i=k}^{P_i^2<=n}\sum_{j=1}^{P_i^{j+1}<=n} f(P_i^j)*s[\left \lfloor \frac{n}{P_i^j} \right \rfloor][i+1]+f(P_i^{j+1})

LL solve(LL a,LL b)//s[a][b]
{
	if(a<prime[b]) return 0;
	LL ans=g[id(a)]-3ll*(b-1);
	for(LL i=b;i<=tot&&1ll*prime[i]*prime[i]<=a;i++)
		for(LL j=1,p=prime[i];p*prime[i]<=a;j++,p*=prime[i])
			ans+=solve(a/p,i+1)*(2*j+1)+(2*(j+1)+1);
	return ans;
}

 

 

完整代碼:

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 1000000
#define LL long long
#define id(i) (((i)<=T)?id1[i]:id2[n/(i)])
LL prime[N],id1[N],id2[N],tot;
bool vis[N];
LL g[N],a[N],T,n,m;
void min25()
{
	tot=m=0;T=sqrt(n+0.5);
	LL i,j;
	for(i=2;i<=T;i++){
		if(!vis[i])prime[++tot]=i;
		for(j=1;j<=tot&&1ll*i*prime[j]<=T;j++){
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}
	for(i=1;i<=n;i=j+1){
		j=n/(n/i);
		a[++m]=n/i;
		if(a[m]<=T)id1[a[m]]=m;
		else id2[n/a[m]]=m;
		g[m]=3ll*(a[m]-1);
	}
	for(j=1;j<=tot;j++)
		for(i=1;i<=m&&prime[j]*prime[j]<=a[i];i++)
			g[i]-=g[id(a[i]/prime[j])]-3ll*(j-1);
}
LL solve(LL a,LL b)
{
	if(a<prime[b]) return 0;
	LL ans=g[id(a)]-3ll*(b-1);
	for(LL i=b;i<=tot&&1ll*prime[i]*prime[i]<=a;i++)
		for(LL j=1,p=prime[i];p*prime[i]<=a;j++,p*=prime[i])
			ans+=solve(a/p,i+1)*(2*j+1)+2*(j+1)+1;
	return ans;
}
LL sum(LL x)
{
	if(x<=0)return 0;
	if(x==1) return 1;
	n=x;min25();
	return (solve(x,1)+1+n)/2ll;
}
int main()
{
	LL a,b;
	scanf("%lld%lld",&a,&b);
	printf("%lld",sum(b)-sum(a-1));
}

 

 

 

Part.2  推式子的具體方法

前情提要:《具體數學》第2章的個人理解

 

做數論題的關鍵就是列出正確的式子,真正的題是不會像這種題一樣板的

如何列出正確的式子呢?

額……這個不太好說吧。。。具體問題具體分析,找規律,補集轉換,容斥原理(一般都有一些數量上限制)

當我們列出了正確的式子,就可以來推它了。。。

當然,在推導之前,也要看一下式子的含義,分析問題的本質(大力質因數分解)

(Freopen大佬有云:如果你一來就推式子,那麼你已經輸了)

所以分析很重要

順便講一下推式子的技巧

1、求和號

可能一開始入手數論的時候不太懂

當兩個求和號沒有任何關聯時,我們就可以直接交換它們

\sum_{i=1}^{n}\sum_{j=1}^nf(i)*f(j)=\sum_{j=1}^n\sum_{i=1}^{n}f(i)*f(j)

如果求和的值(f(i)、f(j))與求和號指標(i、j)無關(如f(i)與j無關,f(j)與i無關)

那麼就可以把裏面的值提出來(如果有關就不能提出來了)

\sum_{i=1}^n\sum_{j=1}^nf(i)*f(j)=\sum_{i=1}^n(f(i)*\sum_{j=1}^nf(j))=(\sum_{i=1}^nf(i))*(\sum_{j=1}^nf(j))

 

如果求和號的指標之間有關怎麼辦?

我們在交換求和號的時候就要有一個原則:

讓每個指標都可以取到自己原來可以取到的值,並且它們之間的關係不變

如:\sum_{i=1}^{n}\sum_{j=1}^{i}f(i)*f(j)

i,j是可以取到n的,i>=j

所以交換求和號之後是:

\sum_{j=1}^{n}\sum_{i=j}^{n}f(i)*f(j)

再來一個

\sum_{i=1}^n\sum_{d|i}f(i)*f(d)            (\sum_{d|i}表示枚舉i的約數d)

i,d都是可以取到n的,並且d|i

所以交換後就是:

\sum_{d=1}^n\sum_{d|i}^nf(i)*f(d)           (\sum_{d|i}^n表示枚舉d的倍數i,且i<=n)

 

如果實在無法理解,你可以自己嘗試把求和的值列成一個矩陣,然後把按行求和換成按列求和來體會一下

 

多個求和號?兩個兩個地交換就是了,雖然慢了點,但是不容易錯

 

2、換元

我們一般會把枚舉倍數的求和形式

\sum_{d=1}^n\sum_{d|i}^nf(i)*f(d)

換成

\sum_{d=1}^n\sum_{i=1}^{\left \lfloor \frac{n}{d} \right \rfloor}f(i*d)*f(d)

其實就是令 i'=i/d,就可以把後面的所有 i 換成 i'*d,很明顯 i' 的取值只有\left \lfloor \frac{n}{d} \right \rfloor

換元的運用遠不止這些。。。

 

3、推式子推到一定程度就可以了,能夠直接求和就直接求了,不行就考慮杜教篩,min_25篩,暴力篩……

 

4、[x=1]=\sum_{i|x}\mu(i)

(莫比烏斯函數的性質,一般都用這個,其實它本質就是狄利克雷卷積式子\epsilon =1*\mu)

(莫比烏斯反演的狄利克雷卷積形式就是f*1=g\Leftrightarrow f=g*\mu)

 

Part.3   杜教篩的使用方法

前置技能:瞭解狄利克雷卷積

 

求:

S(n)=\sum_{i=1}^ni^2*\varphi(i)

設f(i)=i*i*phi(i)

寫成狄利克雷卷積形式:

id\cdot id\cdot \varphi

消掉id^2,讓他卷一個id^2(設g=id^2)

(id\cdot id\cdot \varphi)*(id\cdot id)

寫回來(狄利克雷卷積的第 i 項)

\sum_{d|i}d*d*\varphi(d)*\frac{i}{d}*\frac{i}{d}

=i^2\sum_{d|i}\varphi(d)

因爲\varphi*1=id

所以:

(f*g)(i)=i^3

求個和

\sum_{i=1}^n(f*g)(i)=\sum_{i=1}^ni^3

展開左邊

\sum_{i=1}^n\sum_{d|i}f(\frac{i}{d})*g(d)=\sum_{i=1}^ni^3

\sum_{d=1}^n\sum_{d|i}^{n}f(\frac{i}{d})*g(d)=\sum_{i=1}^ni^3

\sum_{d=1}^n\sum_{i=1}^{\left \lfloor \frac{n}{d} \right \rfloor}f(i)*g(d)=\sum_{i=1}^ni^3

\sum_{d=1}^ng(d)*\sum_{i=1}^{\left \lfloor \frac{n}{d} \right \rfloor}f(i)=\sum_{i=1}^ni^3

把第一項單獨列出來

g(1)*\sum_{i=1}^nf(i)+\sum_{d=2}^ng(d)*\sum_{i=1}^{\left \lfloor \frac{n}{d} \right \rfloor}f(i)=\sum_{i=1}^ni^3

把f(1)+f(2)+...+f(n)換成S(n)

g(1)*S(n)+\sum_{d=2}^ng(d)*S(\left \lfloor \frac{n}{d} \right \rfloor)=\sum_{i=1}^ni^3

最後:

g(1)*S(n)=\sum_{i=1}^ni^3-\sum_{d=2}^ng(d)*S(\left \lfloor \frac{n}{d} \right \rfloor)

S(n)=\sum_{i=1}^ni^3-\sum_{d=2}^nd^2*S(\left \lfloor \frac{n}{d} \right \rfloor)

 

 

 

 

 

 

 

 

 

發佈了134 篇原創文章 · 獲贊 124 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章