杜教筛
Created|Updated
|Post Views:
杜教筛
前置芝士
线性筛
保证每个数只被它的最小质因子筛到。为了达到这个目的,我们在枚举 $i$ 时只枚举比 $i$ 的最小质因子小或相等的质数。即:if(i%p[j]==0) break;
。因此保证了该筛法的线性。
积性函数
若函数 $f(x)$ 满足 $f(pq)=f(p)f(q)$,其中 $\gcd(p,q)=1$,则称函数 $f(x)$ 为积性函数。
大部分数论函数为积性函数。
狄利克雷卷积
定义对于两个函数 $f(x),g(x)$ 的运算 $(f*g)(x)=\sum\limits_{d\mid x}f(d)g(\frac{x}{d})$ 或 $\sum\limits_{d\mid x}f(\frac{x}{d})g(d)$,这个运算叫做 $f$ 与 $g$ 的狄利克雷卷积。
莫比乌斯反演
设函数 $g(x)=\sum\limits_{d\mid x}f(d)$,则 $f(x)=\sum\limits_{d\mid x}g(d)\mu(\frac{x}{d})$。这过程叫做莫比乌斯反演。
简单证明:给定条件 $g(n)=\sum\limits_{d\mid n}f(d)$ 可以写为 $g=f\operatorname{1}$,两边同时卷 $\mu$ 得到 $g\mu=f\operatorname{1}\mu=f\epsilon=f$,即 $f=g\mu$,这即为结论。
正文:杜教筛
杜教筛主要求的是积性函数的前缀和,但线性求法肯定不需要一个专门的名字,因此杜教筛能在 $O(n^{\frac{3}{4}})$ 内求出 $\sum\limits_{i=1}^nf(i)$。
做法
设函数 $g$ 是能够快速算出前缀和的函数,并且 $f*g$ 也能够快速算出前缀和,计 $S(n)=\sum\limits_{i=1}^nf(i)$,那么看推柿子:
$$\sum\limits_{i=1}^n(f*g)(x)$$
$$=\sum\limits_{i=1}^n\sum\limits_{d\mid i}g(d)f(\frac{i}{d})$$
$$=\sum\limits_{d=1}^ng(i)\sum\limits_{i}^{\left\lfloor\frac{n}{i}\right\rfloor}f(i)$$
$$=\sum g(i)S(\left\lfloor\frac{n}{i}\right\rfloor)$$
我们要求的是 $S(n)$,看 $g(1)S(n)$,显然有 $g(1)S(n)=\sum\limits_{i=1}^ng(i)S(\left\lfloor\frac{n}{i}\right\rfloor)-\sum\limits_{i=2}^ng(i)S(\left\lfloor\frac{n}{i}\right\rfloor)$,第一个求和显然等于 $\sum\limits_{i=1}^n(f*g)(x)$,第二个可以用整除分块进行 $O(\sqrt{n})$ 处理。这样就在小于线性的复杂度内求出了 $S(n)$。
值得注意的是,我们可以先进行 $[0,5\times 10^6]$ 内数据的预处理,这样就可以快很多。对于多组数据,我们可以使用 unordered_map
进行常数级记忆化优化来减小复杂度。
其中预处理的范围大概在 $n^{\frac{2}{3}}$,最终的时间复杂度在 $O(n^{\frac{2}{3}})$ 再加上记忆化即可通过模板题 $P4123$。
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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| #include<cstdio> #include<iostream> #include<cstring> #include<queue> #include<stack> #include<algorithm> #include<map> #include<unordered_map>
#include<bitset> #include<set>
#include<deque> #include<cassert> #include<cstdlib> #include<cmath> #include<ctime> #include<vector> #include<random>
#define fi first #define se second #define pb push_back #define mk make_pair #define DBG cerr << __LINE__ << ' ' << __FUNCTION__ << endl
#define DRE default_random_engine #define UID uniform_int_distribution #define y0 Y0 #define y1 Y1
#define pi acos(-1) #define eps (1e-8)
using namespace std;
const int INF = 0x3f3f3f3f; typedef pair<int,int> PII; typedef pair<int,PII> PIII; const int N = 6e5 + 10; const int M = 6e5;
int mu[N],p[N],cnt; long long phi[N]; unordered_map<int,int> sumu; unordered_map<int,long long> sumphi;
void init(){ mu[1]=phi[1]=1; for(int i=2;i<=M;++i){ if(!p[i]) phi[i]=i-1, mu[i]=-1, p[++cnt]=i; for(int j=1;j<=cnt&&i*p[j]<=M;++j){ p[i*p[j]]=1; if(i%p[j]==0){ phi[i*p[j]]=p[j]*phi[i]; break;} phi[i*p[j]]=phi[i]*phi[p[j]], mu[i*p[j]]=-mu[i]; } } for(int i=1;i<=M;++i) mu[i]+=mu[i-1], phi[i]+=phi[i-1]; }
int s1(int n){ return n;} int seps(int n){ return n>=1;} long long sid(long long n){ return n*(n+1)/2;} int smu(int n){ if(n<=M) return mu[n]; if(sumu[n]) return sumu[n]; int res=0; for(unsigned int i=2;i<=n;++i){ int j=n/(n/i); res+=(s1(j)-s1(i-1))*smu(n/i); i=j; } return sumu[n]=seps(n)-res; } long long sphi(unsigned int n){ if(n<=M) return phi[n]; if(sumphi[n]) return sumphi[n]; long long res=0; for(unsigned int i=2;i<=n;++i){ unsigned int j=n/(n/i); res+=1ll*(s1(j)-s1(i-1))*sphi(n/i); i=j; } return sumphi[n]=sid(n)-res; }
signed main(){ ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); int n,t; init(); cin>>t; while(t--) cin>>n,cout<<sphi(n)<<" "<<smu(n)<<endl; return 0; }
|