原来这东西这么简单的么……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(1sim n^c)) 的值(其中 (c>frac{1}{2})),那么复杂度将变成
当 (c=frac23) 时达到最小,为 (O(n^{frac23}))。
对于记忆化数组,由于每次递归的参数都是 (leftlfloorfrac nx
ight
floor) 的形式,且对于 (x>n^{frac13}) 已经预处理过了,所以我们只需开一个 (n^{frac13}) 大小的数组表示 (x) 即可。
讲完啦!看道模板吧:Luogu4213【模板】杜教筛(Sum)。
题目很简单了,由于 (varphi*1=operatorname{id},mu*1=epsilon),于是 (g,f*g) 就构造出来了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned int uint;
typedef pair<ll,int> 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
",ans.first,ans.second);
}
return 0;
}
以后会随缘更几道例题吧(咕咕咕