View on GitHub

Wenchong Huang

旧博客,文章搬迁完后删除

返回首页 返回专题

杜教筛 学习笔记

导入

这种筛法首次出现于外国的什么鬼网站,由于杜瑜皓是将其引入中国的第一人,故得名“杜教筛”

网上介绍这种筛法往往扯上狄利克雷卷积,然后说一大堆没用的东西,我实在看不懂,就问了yxq巨佬,巨佬开口就先鄙视了一番网上的博客,然后花了不到5分钟就把我教会了qwq,太神了!

正文

嗯,来做一道题:求φ(n)的前缀和

什么?你觉得很简单?线性筛一下就做出来了?

那我告诉你n的范围是10^9呢……O(n)爆炸了!

于是我们要用复杂度更低的方法。

先来画柿子!

然后就可以递归求解了!

什么?递归?i=2~n的枚举不是O(n)的吗?这复杂度明显更高了呀!

看到整除还不整除分块!

递归的退出条件?

线性筛出前N个的T(n),然后当n<N时返回T(n)即可

至于要筛前多少个,可以自己调一下参数,调到你觉得最快就行了

如果你参数调的好,时间复杂度是可以达到O(n^(2/3))的

需要加一个优化。因为杜教筛的过程中会计算很多重复的东西,所以可以用一个蛤希表来记录已经计算过的值,以后算到直接调用就可以了。如果像我一样比较懒,蛤希表开map也可以

代码实现

下面的代码并没有加蛤希表优化

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N=7000000;
int prime[N],tot=0;
LL phi[N+10];
bool mark[N+10];

void Init()
{
	phi[1]=1;
	for(int i=2;i<=N;i++)
	{
		if(!mark[i]) prime[++tot]=i,phi[i]=i-1;
		for(int j=1;j<=tot&&i*prime[j]<=N;j++)
		{
			mark[i*prime[j]]=1;
			if(i%prime[j]) phi[i*prime[j]]=(prime[j]-1)*phi[i];
			else phi[i*prime[j]]=prime[j]*phi[i];
		}
	}
	for(int i=1;i<=N;i++) phi[i]+=phi[i-1];
}

LL Sphi(LL n)
{
	if(n<=N) return phi[n];
	LL res=n*(n+1)/2,div;
	for(LL i=2;i<=n;i=div+1)
	{
		div=n/(n/i);
		res-=(div-i+1)*Sphi(n/i);
	}
	return res;
}

int main()
{
	Init();
	LL n;
	cin>>n;
	cout<<Sphi(n)<<endl;
	return 0;
}

/*
Description : 求sigma{phi(i)},i=1~n
Sample Input : 1000000000
Sample Output : 303963551173008414
HINT : n<=10^9
Time Limit : 1s
Memory Limit : 512MB
*/