Part.1 min25篩
前情提要:應用特別廣泛,代碼難度一般,但理解難度較大
若f(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值
這裏利用了f是積性函數的條件
理解:用前k-1個質數來篩,把沒篩掉的數,作爲Pk的倍數(這個倍數一定會前k-1個質數互質)(當然,這個倍數*Pk應該小於n)來確定Pk需要篩哪些點。由於前k-1個質數也是剩下的數,所以還要加上(就是,即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})
我們明顯知道當且時(篩的臨界點),再讓來篩已經沒有意義了,即剩下的都是質數了
所以此時的s[n][k]就是g[n][k]-f(P1)-f(P2)-...-f(P_{k-1})
爲了方便,我們把這個臨界點的k值即爲 |P|
於是我們有轉移:
我們現在相當於在執行篩質數的逆過程,即用質數組合出合數的過程
我們可以寫出一個等價的狀態定義:s[n][k]表示用第k~|P|的質數,組合出的數(包括質數本身)的 f 值之和
注意:這裏新組合的數指的是將最小質因子大於等於Pk的數
理解:
在單獨計算質數的答案
枚舉當前質數Pi,以及它的冪次j,作爲最小質因子,用與前k個質數互質的數來作爲他的倍數,
求出曾經被它叉掉的(即以它作爲最小質因子,新組合出來的)數的 f 值之和
由於這樣只能求它與其他質數組合出的合數的答案
所以我們還要加上,讓他自己和自己也可以組合出合數
最後加上f(1)的答案就好啦
時間複雜度?(差不多線性),但是在n比較小的情況下是
具體實現:
拿一道例題來講
最小公倍數計數
定義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的前綴和
先放上套路式:
首先,我們發現n的取值只有種(因爲每次只調用了作爲下標())
於是可以把這些位置預處理出來,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) 就是在計算
s[n][k]部分的計算就比較好理解了(遞歸計算)(直接套式子)
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、求和號
可能一開始入手數論的時候不太懂
當兩個求和號沒有任何關聯時,我們就可以直接交換它們
如果求和的值(f(i)、f(j))與求和號指標(i、j)無關(如f(i)與j無關,f(j)與i無關)
那麼就可以把裏面的值提出來(如果有關就不能提出來了)
如果求和號的指標之間有關怎麼辦?
我們在交換求和號的時候就要有一個原則:
讓每個指標都可以取到自己原來可以取到的值,並且它們之間的關係不變
如:
i,j是可以取到n的,i>=j
所以交換求和號之後是:
再來一個
(表示枚舉i的約數d)
i,d都是可以取到n的,並且d|i
所以交換後就是:
(表示枚舉d的倍數i,且i<=n)
如果實在無法理解,你可以自己嘗試把求和的值列成一個矩陣,然後把按行求和換成按列求和來體會一下
多個求和號?兩個兩個地交換就是了,雖然慢了點,但是不容易錯
2、換元
我們一般會把枚舉倍數的求和形式
換成
其實就是令 i'=i/d,就可以把後面的所有 i 換成 i'*d,很明顯 i' 的取值只有個
換元的運用遠不止這些。。。
3、推式子推到一定程度就可以了,能夠直接求和就直接求了,不行就考慮杜教篩,min_25篩,暴力篩……
4、
(莫比烏斯函數的性質,一般都用這個,其實它本質就是狄利克雷卷積式子)
(莫比烏斯反演的狄利克雷卷積形式就是)
Part.3 杜教篩的使用方法
前置技能:瞭解狄利克雷卷積
求:
設f(i)=i*i*phi(i)
寫成狄利克雷卷積形式:
消掉id^2,讓他卷一個id^2(設g=id^2)
寫回來(狄利克雷卷積的第 i 項)
因爲
所以:
求個和
展開左邊
把第一項單獨列出來
把f(1)+f(2)+...+f(n)換成S(n)
最後: