杜教筛

亚线性筛之一

杜教筛

前置知识

积性函数

前言

首先先看一下积性函数吧,积性函数就是在对于所有的互质的 aabb ,总有 f(ab)=f(a)f(b)f(ab) = f(a)f(b) , 则 f(x)f(x) 为积性函数。

比较常见的主要有:

d(x)=in1σ(x)=iniφ(x)=i=1x1[gcd(x,i)=1]μ(x)={1x=1(1)ki=1kqi=10max{qi}>1ϵ(x)=[x=1]d(x)= \sum_{i \mid n} 1 \\ \sigma (x) = \sum_{i \mid n} i \\ \varphi (x) = \sum_{i=1}^x 1 [gcd(x,i)=1] \\ \mu (x) = \left\{ \begin{aligned} &1 &x=1 \\ &(-1)^k &\prod_{i=1}^k q_i=1 \\ &0&\max\left\{ q_i \right\} > 1 \end{aligned} \right. \\ \epsilon (x) = [x=1]

积性函数有以下性质:
f(x),g(x)f(x),g(x)为积性函数,则,

h(x)=f(xp)h(x)=fp(x)h(x)=f(x)g(x)h(x)=dxf(d)g(xd)h(x) = f(x^p) \\ h(x) = f^p(x) \\ h(x) = f(x)g(x) \\ h(x) = \sum_{d \mid x} f(d)g(\frac{x}{d})

中的h(x)h(x)也为积性函数。

杜教筛主要是用来求像这些积性函数的前缀和。

正文

对于求一个数论函数的前缀和,杜教筛可以在低于线性时间的复杂度内求解。

对于数论函数 ff , 要求计算 S(n)=i=1nf(i)S(n) = \sum_{i=1}^n f(i)

首先构造一个 S(n)S(n) 关于S(ni)S(\left\lfloor\frac{n}{i}\right\rfloor) 的递推式

对于任意一个数论函数 gg ,必须满足

i=1ndig(d)f(id)=i=1ng(i)S(ni)    i=1n(fg)(i)=i=1ng(i)S(ni)\sum_{i=1}^n \sum_{d \mid i}g(d)f(\frac{i}{d})= \sum_{i=1}^ng(i)S(\left\lfloor\frac{n}{i}\right\rfloor) \iff \sum_{i=1}^n(f \ast g)(i) = \sum_{i=1}^ng(i)S(\left\lfloor\frac{n}{i}\right\rfloor)

简单的证明:

i=1ndig(d)f(id)=i=1nj=1nig(i)f(j)=i=1ng(i)j=1nif(j)=i=1ng(i)S(ni)\sum_{i=1}^n \sum_{d \mid i}g(d)f(\frac{i}{d})\\ = \sum_{i=1}^n\sum_{j=1}^{\left\lfloor\frac{n}{i}\right\rfloor} g(i)f(j)\\ = \sum_{i=1}^ng(i)\sum_{j=1}^{\left\lfloor\frac{n}{i}\right\rfloor} f(j)\\ = \sum_{i=1}^ng(i)S(\left\lfloor\frac{n}{i}\right\rfloor) \\

求出递推式

g(1)S(n)=i=1n(fg)(i)i=2ng(i)S(ni)g(1)S(n)=\sum_{i=1}^n(f \ast g)(i)-\sum_{i=2}^ng(i)S(\left\lfloor\frac{n}{i}\right\rfloor)

可以用数论分块对后半部分快速求出结果。

问题

S1(n)=i=1nμ(i)S2(n)=i=1nφ(i)S_1(n)=\sum_{i=1}^n \mu(i)\\ S_2(n)=\sum_{i=1}^n \varphi(i)

第一部分求莫比乌斯函数前缀和

ϵ=μ1ϵ=dnμ(d)S1(n)=i=1nϵ(i)i=2nS1(ni)=1i=2nS1(ni)\because \epsilon =\mu \ast 1 \\ \therefore \epsilon = \sum_{d \mid n}\mu (d) \\ S_1(n) = \sum_{i=1}^n \epsilon(i) -\sum_{i=2}^nS_1(\left\lfloor\frac{n}{i}\right\rfloor) \\ = 1-\sum_{i=2}^nS_1(\left\lfloor\frac{n}{i}\right\rfloor)

直接整除分块

第二部分求欧拉函数前缀和

φ1=IDi=1n(φ1)(i)=i=1n1S2(ni)i=1nID(i)=i=1n1S2(ni)12n(n+1)=i=1nS2(ni)S2(n)=12n(n+1)i=2nS2(ni)\because \varphi \ast 1 =ID \\ \therefore \sum_{i=1}^n (\varphi \ast 1)(i)=\sum_{i=1}^n 1 \cdot S_2(\left\lfloor\frac{n}{i}\right\rfloor) \\ \sum_{i=1}^n ID(i)= \sum_{i=1}^n 1 \cdot S_2(\left\lfloor\frac{n}{i}\right\rfloor) \\ \frac{1}{2}n(n+1) = \sum_{i=1}^nS_2(\left\lfloor\frac{n}{i}\right\rfloor) \\ S_2(n)=\frac{1}{2}n(n+1)-\sum_{i=2}^nS_2(\left\lfloor\frac{n}{i}\right\rfloor) \\

时间复杂度O(n23)O(n ^{\frac{2}{3}})

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e6+10;
int T,cnt,n;
ll mu[maxn],prime[maxn],S1[maxn],S2[maxn];
bool vis[maxn];
map<ll,ll> eit;
void Mu()
{
mu[1]=1;
for(int i=2;i<=2e6;i++)
{
if(!vis[i])
{
prime[++cnt]=i;
mu[i]=-1;
}
for(int j=1;j<=cnt&&prime[j]*i<=2e6;j++)
{
vis[prime[j]*i]=true;
if(i%prime[j]==0)
{
mu[prime[j]*i]=0;
break;
}
mu[prime[j]*i]=-mu[i];
}
}
for(int i=1;i<=2e6;i++)
S1[i]=S1[i-1]+mu[i];
}
ll S_mu(ll x)
{
if(x<=2e6)return S1[x];
if(eit[x])return eit[x];
ll res=1;
for(ll l=2,r;l<=x;l=r+1)
{
r=x/(x/l);
res-=S_mu(x/l)*(r-l+1);
}
return eit[x]=res;
}
ll S_phi(ll x)
{
ll res=0;
for(ll l=1,r;l<=x;l=r+1)
{
r=x/(x/l);
res+=(S_mu(r)-S_mu(l-1))*(x/l)*(x/l);
}
return (res-1)/2+1;
}
int main()
{
Mu();
scanf("%d",&T);
while(T--)
{
scanf("%lld",&n);
printf("%lld %lld\n",S_phi(n),S_mu(n));
}
return 0;
}

对于一些筛法的小技巧:

对于比较大的数据时,筛法在筛前半段时花费的时间显然是比较长的,这时我们可以直接线性筛一遍先记录下来,在求后半段时就可以省下大部分时间,称为根号分治

作者

Jekyll_Y

发布于

2022-07-19

更新于

2023-03-02

许可协议

评论