杜教筛瞎扯

原来这东西这么简单的么……qwq


杜教筛用来快速筛出一系列数论函数的前缀和,并且不要求积性。
我们设要筛的函数为 \(S(n)=\sum_{i=1}^{n}f(i)\),如果能找到一个函数 \(g\),考虑如下的过程:

\[\sum_{i=1}^n(f*g)(i)=\sum_{i=1}^n\sum_{d\mid i}f(d)g\left(\frac{i}{d}\right)=\sum_{d=1}^ng(d)\sum_{k=1}^{\left\lfloor\frac{n}{d}\right\rfloor}f(k)=\sum_{d=1}^ng(d)S\left(\left\lfloor\frac{n}{d}\right\rfloor\right) \]

我们将右边和式的第一项 \(g(1)S(n)\) 提出来放到左边,得到

\[g(1)S(n)=\sum_{i=1}^n(f*g)(i)-\sum_{i=2}^ng(i)S\left(\left\lfloor\frac{n}{i}\right\rfloor\right) \]

可以看到我们已经得到了一个关于 \(S(n)\) 的递归计算式。如果我们能快速求出 \(f*g,g\) 的前缀和(通常都是 \(O(1)\),不过只要计算这俩的复杂度不超过单次杜教筛的复杂度也可以,杜教筛套杜教筛),就可以数论分块来递归计算 \(S\) 了。
下面来计算一下复杂度,略去递归的话单次计算显然是 \(O(\sqrt n)\) 的,也就是说如果 \(f*g,g\) 的复杂度不超过 \(O(\sqrt n)\) 是不影响杜教筛总复杂度的。复杂度的总计算式就是

\[O\left(\sum_{i=1}^{\sqrt n}\sqrt i+\sum_{i=1}^{\sqrt n}\sqrt{\left\lfloor\frac{n}{i}\right\rfloor}\right)=O\left(\sum_{i=1}^{\sqrt n}\sqrt{\left\lfloor\frac{n}{i}\right\rfloor}\right)=O\left(\int_{1}^{\sqrt n}\sqrt{\frac{n}{x}}{\rm d}x\right)=O\left(n^{\frac{3}{4}}\right) \]

但是这样还不够快。注意到我们递归计算的 \(n\) 是一个这样的数列:

\[1,2,\dots,\sqrt n,\left\lfloor\frac n1\right\rfloor,\left\lfloor\frac n2\right\rfloor,\dots,\left\lfloor\frac n{\sqrt n}\right\rfloor \]

如果对于较小的 \(n\) 我们仍 \(O(\sqrt n)\) 计算显然是非常不明智的。如果能我们预处理出 \(S(1\sim n^c)\) 的值(其中 \(c>\frac{1}{2}\)),那么复杂度将变成

\[O\left(n^c+\sum_{i=1}^{n^{1-c}}\sqrt{\left\lfloor\frac ni\right\rfloor}\right)=O\left(n^c+\int_1^{n^{1-c}}\sqrt{\frac nx}{\rm d}x\right)=O\left(n^c+n^{1-\frac12 c}\right) \]

\(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;
}

以后会随缘更几道例题吧(咕咕咕

你可能感兴趣的:(杜教筛瞎扯)