功能
杜教篩可以在非線性的時間內求出極性函數的前綴和。
洛谷給出的模板:
對於n()求出:
注:爲歐拉函數.爲莫比烏斯函數,定義:
所需要的知識
積性函數
引入幾個積性函數:
其中,前三種爲完全積性函數
狄利克雷卷積
設兩個數論函數則,它們的狄利克雷卷積是:
狄利克雷卷積與其數論函數成羣,滿足結合律,交換律.同時具有封閉性,且擁有單位元及逆元
函數爲狄利克雷卷積的單位元,這裏引入幾個性質:
莫比烏斯反演
若
則有
證明:
不妨設
則
由狄利克雷卷積性質1,即有
得證
杜教篩
現在,題目要求我們得出積性函數的前綴和,我們設:
這時我們引入一個積性函數考慮的前綴和,則有以下:
第三行的推定:我們枚舉,對於每一個它的係數都是
這時發現,如果我們當時的上式值,就便於求出而
根據(2)推定,便有杜教篩的核心公式
如果我們可以找到合適的積性函數,使快速求出和,我們就可以使用數論分塊處理.
時間複雜度
如果和的求解爲的,則杜教篩時間複雜度爲
總結
核心思想,就是設法構建杜教篩的核心公式(4),合理利用迪利克雷卷積的3個性質,以及常用積性函數,找到合適的令和可以在很短的時間內求出,最後進行數論分塊.
模板
1.的前綴和
由迪利克雷卷積性質2,我們設
則,
顯然
,
可以由通項公式進行求解
這裏給出代碼(轉自洛谷):
#define ll long long
ll phi[MAXN+10];
unordered_map<int,ll> ansphi;
ll S_phi(int n) {
if (n<=MAXN) return phi[n];
if (ansphi.find(n)!=ansphi.end()) return ansphi[n];
ll ans=1ll*n*(n+1)/2;
for (int l=2,r;l<=n;l=r+1)
r=n/(n/l),ans-=(r-l+1)*S_phi(n/l);
return ansphi[n]=ans;
}
這裏解釋:phi數組是預先處理好的歐拉函數的前綴和,(前MAXN項)ansphi是執行記憶化過程中得出的事先未能處理到的歐拉函數前綴和.函數中的for就是數論分塊求的過程,首先我們的所以在這裏就是求
1.的前綴和
由迪利克雷卷積性質1,設則
則
這裏給出代碼(轉自洛谷):
ll mu[MAXN];
unordered_map<int,ll> ansmu;
ll S_mu(int n) {
if(n <= MAXN) return mu[n];
if(ansmu.find(n) != ansmu.end()) return ansmu[n];
ll ret = 1ll;
for(int l = 2, r; l <= n; l = r + 1) {
r = n / (n / l); ret -= (r - l + 1) * S_mu(n / l);
} return ansmu[n] = ret;
}
解釋mu,ansmu意義同上.
這裏給出的打表方法(線性,類比歐拉函數的線性篩法.及性質):
bool check[MAX+10];
int prime[MAX+10];
int mu[MAX+10];
void Moblus()
{
memset(check,false,sizeof(check));
mu[1] = 1;
int tot = 0;
for(int i = 2; i <= MAX; i++)
{
if( !check[i] )
{
prime[tot++] = i;
mu[i] = -1;
}
for(int j = 0; j < tot; j++)
{
if(i * prime[j] > MAX) break;
check[i * prime[j]] = 1;
if( i % prime[j] == 0)
{
mu[i * prime[j]] = 0;
break;
}
else
mu[i * prime[j]] = -mu[i];
}
}
下面是模板代碼:
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>
#include <set>
#include <cstdio>
#include <cstdlib>
#define max(x, y) ((x)>(y)?(x):(y))
#define ll long long
using namespace std;
const int N = 2e6+5;
ll v[N], pri[N], phi[N], mu[N], cnt;
ll tot;
void init(int n)
{
tot = n;
mu[1] = phi[1] = 1;
for (int i = 2; i <= n; i++)
{
if (v[i] == 0)
{
v[i] = i, pri[++cnt] = i;
phi[i] = i - 1;
mu[i] = -1;
}
for (int j = 1; j <= cnt; j++)
{
if (pri[j] > v[i] || pri[j] * i > n) break;
v[i * pri[j]] = pri[j];
phi[i * pri[j]] = phi[i] * (i % pri[j] ? pri[j]-1: pri[j]);
}
for (int j = 1; j <= cnt; j++)
{
if (i * pri[j] > n) break;
if (i % pri[j] == 0){mu[i*pri[j]] = 0; break;}
else mu[i*pri[j]] = -mu[i];
}
}
for (int i = 2; i <= n; i++) mu[i] += mu[i-1], phi[i] += phi[i-1];
}
map<int, ll> ansphi, ansmu;
ll S_phi(int n)
{
if (n < tot) return phi[n];
if (ansphi.find(n) != ansphi.end()) return ansphi[n];
ll ans = 1ll * n * (n + 1) / 2;
for (int l = 2, r; l <= n; l = r + 1)
{r=n/(n/l); ans -= (r - l + 1) * S_phi(n/l);}
return ansphi[n] = ans;
}
ll S_mu(int n)
{
if (n <tot) return mu[n];
if (ansmu.find(n) != ansmu.end()) return ansmu[n];
ll ans = 1ll;
for (int l = 2, r; l <= n; l = r + 1)
{r = n / (n / l); ans -= (r - l + 1) * S_mu(n / l);}
return ansmu[n] = ans;
}
int main()
{
int t;
init(2e6);
cin >> t;
while(t--)
{
int n;
scanf("%d", &n);
printf("%lld %lld\n", S_phi(n), S_mu(n));
}
}
一些杜教篩例子
求,設
故可求出,也可以求出.