原来这东西这么简单的么……qwq
杜教筛用来快速筛出一系列数论函数的前缀和,并且不要求积性。
我们设要筛的函数为 \(S(n)=\sum_{i=1}^{n}f(i)\),如果能找到一个函数 \(g\),考虑如下的过程:
我们将右边和式的第一项 \(g(1)S(n)\) 提出来放到左边,得到
可以看到我们已经得到了一个关于 \(S(n)\) 的递归计算式。如果我们能快速求出 \(f*g,g\) 的前缀和(通常都是 \(O(1)\),不过只要计算这俩的复杂度不超过单次杜教筛的复杂度也可以,杜教筛套杜教筛),就可以数论分块来递归计算 \(S\) 了。
下面来计算一下复杂度,略去递归的话单次计算显然是 \(O(\sqrt n)\) 的,也就是说如果 \(f*g,g\) 的复杂度不超过 \(O(\sqrt n)\) 是不影响杜教筛总复杂度的。复杂度的总计算式就是
但是这样还不够快。注意到我们递归计算的 \(n\) 是一个这样的数列:
如果对于较小的 \(n\) 我们仍 \(O(\sqrt n)\) 计算显然是非常不明智的。如果能我们预处理出 \(S(1\sim n^c)\) 的值(其中 \(c>\frac{1}{2}\)),那么复杂度将变成
当 \(c=\frac23\) 时达到最小,为 \(O(n^{\frac23})\)。
对于记忆化数组,由于每次递归的参数都是 \(\left\lfloor\frac nx\right\rfloor\) 的形式,且对于 \(x>n^{\frac13}\) 已经预处理过了,所以我们只需开一个 \(n^{\frac13}\) 大小的数组表示 \(x\) 即可。
讲完啦!看道模板吧:Luogu4213【模板】杜教筛(Sum)。
题目很简单了,由于 \(\varphi*1=\operatorname{id},\mu*1=\epsilon\),于是 \(g,f*g\) 就构造出来了。
#include
using namespace std;
typedef long long ll;
typedef unsigned int uint;
typedef pair pli;
const int N=pow(INT_MAX,0.666)+1;
const int M=pow(INT_MAX,0.333)+1;
int n,pri[N+5];
ll phi[N+5],mu[N+5];
bool v[N+5],vis[M+5];
pli res[M+5];
pli DuSieve(int x)
{
if(x<=N) return make_pair(phi[x],mu[x]);
int nx=n/x;
if(!vis[nx])
{
vis[nx]=1;
ll v1=x*(1LL*x+1)/2; int v2=1;
for(uint l=2,r;l<=x;l=r+1)
{
r=x/(x/l); pli t=DuSieve(x/l);
v1-=(r-l+1)*t.first,v2-=(r-l+1)*t.second;
}
res[nx]=make_pair(v1,v2);
}
return res[nx];
}
int main()
{
mu[1]=phi[1]=1;
for(int i=2;i<=N;++i)
{
if(!v[i]) pri[++pri[0]]=i,mu[i]=-1,phi[i]=i-1;
for(int j=1;j<=pri[0]&&i*pri[j]<=N;++j)
{
v[i*pri[j]]=1;
if(i%pri[j]==0) {phi[i*pri[j]]=phi[i]*pri[j]; break;}
mu[i*pri[j]]=-mu[i],phi[i*pri[j]]=(pri[j]-1)*phi[i];
}
phi[i]+=phi[i-1],mu[i]+=mu[i-1];
}
int Case; scanf("%d",&Case);
while(Case--)
{
scanf("%d",&n);
memset(vis,0,sizeof(vis));
pli ans=DuSieve(n);
printf("%lld %d\n",ans.first,ans.second);
}
return 0;
}
以后会随缘更几道例题吧(咕咕咕