一句话总结杜教筛:
综合起来,杜教筛算法可以概况为:用狄利克雷卷积构造递推式,编程时用整除分块和线性筛优化,算法复杂度达到了O ( n 2 3 ) O(n^{\frac{2}{3}}) O ( n 3 2 ) 。
只要给你的函数的卷积和函数可以方便的表示出来且好计算,那么杜教筛就有用武之地。
现在开始推倒杜教筛的过程
要求函数:f ( x ) f(x) f ( x ) 的前缀和 s ( x ) s(x) s ( x )
我们设两个函数为h ( x ) g ( x ) h(x) g(x) h ( x ) g ( x ) 有h ( x ) = f ( x ) ∗ g ( x ) h(x)=f(x)*g(x) h ( x ) = f ( x ) ∗ g ( x )
∑ i = 1 n h ( x ) = ∑ i = 1 n ∑ j ∣ i f ( i j ) g ( j ) \sum_{i=1}^nh(x)=\sum_{i=1}^n\sum_{j|i}f(\frac{i}{j})g(j)
i = 1 ∑ n h ( x ) = i = 1 ∑ n j ∣ i ∑ f ( j i ) g ( j )
∑ i = 1 n h ( x ) = ∑ j = 1 n ∑ j ∣ i n f ( i j ) g ( j ) \sum_{i=1}^nh(x)=\sum_{j=1}^n\sum_{j|i}^nf(\frac{i}{j})g(j)
i = 1 ∑ n h ( x ) = j = 1 ∑ n j ∣ i ∑ n f ( j i ) g ( j )
设k=i/j
∑ i = 1 n h ( x ) = ∑ j = 1 n g ( j ) ∑ k = 1 n j f ( k ) \sum_{i=1}^nh(x)=\sum_{j=1}^ng(j)\sum_{k=1}^{\frac{n}{j}}f(k)
i = 1 ∑ n h ( x ) = j = 1 ∑ n g ( j ) k = 1 ∑ j n f ( k )
∑ i = 1 n h ( x ) = ∑ j = 1 n g ( j ) s ( n j ) \sum_{i=1}^nh(x)=\sum_{j=1}^ng(j)s(\frac{n}{j})
i = 1 ∑ n h ( x ) = j = 1 ∑ n g ( j ) s ( j n )
把我们目标s ( n ) s(n) s ( n ) 分离出来
s ( n ) g ( 1 ) = ∑ i = 1 n h ( i ) − ∑ j = 2 n s ( ⌊ n j ⌋ ) g ( j ) s(n)g(1)=\sum_{i=1}^nh(i)-\sum_{j=2}^ns(\lfloor\frac{n}{j}\rfloor )g(j)
s ( n ) g ( 1 ) = i = 1 ∑ n h ( i ) − j = 2 ∑ n s ( ⌊ j n ⌋ ) g ( j )
真就这么魔幻的算出来了。
这是一个递归计算的式子,大佬告诉我们其时间复杂度为O ( n 2 3 ) O(n^{\frac{2}{3}}) O ( n 3 2 ) (前提h ( i ) , g ( i ) h(i),g(i) h ( i ) , g ( i ) 足够简单易求
基础推导1
首先关于ϕ \phi ϕ 函数
n = ϕ ∗ 1 n=\phi * 1
n = ϕ ∗ 1
证明,1 n , 2 n , 3 n , 4 n , 5 n , . . . . . . n n \frac{1}{n},\frac{2}{n},\frac{3}{n},\frac{4}{n},\frac{5}{n}, ......\frac{n}{n} n 1 , n 2 , n 3 , n 4 , n 5 , . . . . . . n n 化简,每一个分母显然都是n的因数,且每个分母为y的分子都有ϕ ( y ) \phi(y) ϕ ( y ) 个,所以相加为n n n 。
然后我们发现将这个式子套在杜教筛上,g函数和h函数都十分简单易求。
将g函数和h函数带入
s ( n ) = n ∗ ( 1 + n ) 2 − ∑ j = 2 n s ( ⌊ n j ⌋ ) s(n)=\frac{n*(1+n)}{2}-\sum_{j=2}^ns(\lfloor\frac{n}{j}\rfloor )
s ( n ) = 2 n ∗ ( 1 + n ) − j = 2 ∑ n s ( ⌊ j n ⌋ )
记忆化递归求解即可。
基础推导2
有
E = μ ∗ 1 E=\mu*1
E = μ ∗ 1
不会左转莫比乌斯反演
然后又可以愉快的套杜教筛
s ( n ) = 1 − ∑ j = 2 n s ( ⌊ n j ⌋ ) s(n)=1-\sum_{j=2}^ns(\lfloor\frac{n}{j}\rfloor )
s ( n ) = 1 − j = 2 ∑ n s ( ⌊ j n ⌋ )
记忆化递归求解即可。
思路:没有思路,就是板子
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 #include <bits/stdc++.h> #include <tr1/unordered_map> using namespace std;#define int long long const int M=5e6 ;int n,pri[M+5 ],cntp,phi[M+5 ],mu[M+5 ],vis[M+5 ];tr1::unordered_map <int ,int > sphi,smu; void pre () { phi[1 ]=1 ;mu[1 ]=1 ; for (int i=2 ;i<=M;i++){ if (!vis[i]){ pri[++cntp]=i;mu[i]=-1 ;phi[i]=i-1 ; } for (int j=1 ;j<=cntp&&pri[j]*i<=M;j++){ vis[i*pri[j]]=1 ; if (i%pri[j]!=0 ){ mu[i*pri[j]]=mu[i]*-1 ;phi[i*pri[j]]=phi[i]*phi[pri[j]]; } else { mu[i*pri[j]]=0 ;phi[i*pri[j]]=phi[i]*pri[j];break ; } } } for (int i=1 ;i<=M;i++) phi[i]+=phi[i-1 ],mu[i]+=mu[i-1 ]; } int getsumphi (int x) { if (x<=M) return phi[x]; if (sphi[x]) return sphi[x]; int res=x*(x+1 )/2 ; for (int S=2 ,E;S<=x;S=E+1 ){ E=x/(x/S); res-=getsumphi (x/S)*(E-S+1 ); } sphi[x]=res;return res; } int getsummu (int x) { if (x<=M) return mu[x]; if (smu[x]) return smu[x]; int res=1 ; for (int S=2 ,E;S<=x;S=E+1 ){ E=x/(x/S); res-=getsummu (x/S)*(E-S+1 ); } smu[x]=res;return res; } signed main () { pre (); int T;cin>>T; while (T--){ cin>>n;cout<<getsumphi (n)<<" " <<getsummu (n)<<"\n" ; } return 0 ; }