现在杜教筛好像是个人都会了 orz。
所以我写篇博客复习一下,这里不讲原理只讲优化技巧的和如何使用
先贴两个学习原理的博客:
http://www.cnblogs.com/abclzr/p/6242020.html
http://www.cnblogs.com/candy99/p/dj_s.html
然后我们来讲一下,怎么使用,下面均以要 求和的n为109 为讨论前提
一般取用来卷积的辅助函数g为恒等函数:g(n)=1 ,n=1,2,3,…………(有时取g(n)=mu(n) 啥的也有奇效,反正是玄学)
这时杜教筛的公式就能化简为:
我们一般只用红框里的两个公式。
杜教筛不要求函数一定要有积性,而是要求两点
- f(i)在106以内的所有前缀和能在接近O(n)复杂度内全部处理出来。
- 狄利克雷卷积的前缀和——h(n) ,在1e9 内的任意一项h(m),能在O(sqrt(m)) 以内的复杂度内求出来。
使用步骤一:预处理106 以内的s[i]
这一步如果f(i)是积性函数,可以直接用线性筛法直接搞定,如果不是的话,就要可能要注意分析性质瞎搞了(比如搞一些预处理,使得f(i)可以O(1)求之类的骚操作)。
注意这里只是以106为例,因为后面的计算过程常数比较大,追求速度的话,最好要能处理到2*106。
使用步骤二:写快速计算h函数第k项的函数 h(int k)
这里求h,有以下几种方法
-
用公式推出
的通项看是否有优良的性质。可以快速搞出前n项和
- 对
打表找规律
- 对
打表找规律
使用步骤三:写个记忆化搜索计算s(n)
设我们预处理出s[i]的范围是i∈[1,UB】。
通用的写法
若之前算过n 我们则返回我们记下的值,因为n比较大不能用数组直接存,最好用哈希表。
若n<=UB,我们返回预处理好的s[i]
否则使用公式递归计算
要注意到(n/i) 只有2*sqrt(n) 不同的取值,直接用一下数论分块就行了(如果不知道啥是数论分块的话,你百度一下就行 了)
然后注意把值存到哈希表内。
代码如下
1 long long S(long long n) 2 { 3 if(n<=UB) 4 { 5 return s[n]; 6 } 7 long long ans=0; 8 ans=table.find(n);///查询哈希表中是否有n 9 if(ans==-1) 10 { 11 long long i,j; 12 ans=0; 13 for(i=2; i<=n; i++) 14 { 15 j=i; 16 i=n/(n/i); 17 ans+=(i-j+1)*S(n/j); 18 } 19 ans=(h(n)-ans); 20 table.insert(n,ans);/// 将n对应的值存入哈希表 21 } 22 return ans; 23 }