用杜教筛求解数论函数前缀和
杜教筛用来求数论函数\(f\)前缀和。复杂度为\(O(n^{\frac{2}{3}})\)
前提
如果我们要求\(S(n)=\sum\limits_{i=1}^nf(i)\),那么需要找到一个数论函数\(g\),满足\(g\)的前缀和可以非常快速的求出来,并且\(g*f\)的前缀和可以非常快速的求出来。
推导
既然\(g*f\)的前缀和可以非常快速的求出来,我们就求\(g*f\)的前缀和。
即\(\sum\limits_{i=1}^n\sum\limits_{d|i}g(\frac{i}{d})f(d)\)。
然后我们想得到的是\(\sum\limits_{i=1}^nf(i)\)。所以我们让上面的式子减去一个\(\sum\limits_{i=1}^n\sum\limits_{d|i,d\neq i}g(\frac{i}{d})f(d)\)。
对于后面这个式子,我们用\(\frac{n}{d}\)来代替\(d\),就变成了
\[\sum\limits_{i=1}^n\sum\limits_{d\neq 1,d|i}g(d)f(\frac{i}{d}) \]
\[=\sum\limits_{d=2}^n g(d)\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i) \]
\[=\sum\limits_{d=2}^n g(d)S(\lfloor\frac{n}{d}\rfloor) \]
因为\(g\)的前缀和可以快速求出,所以直接数论分块,后面的\(S(\frac{n}{d})\)直接递归就好了。
这样我们得到的是\(\sum\limits_{i=1}^ng(1)f(i)=g(1)\sum\limits_{i=1}^n f(i)\),所以答案除以\(g(1)\)(一般为1)就好了。
例子
以求\(\varphi\)的前缀和为例。因为\(f*1=Id\),\(1\)和\(Id\)的前缀和都非常好求,所以我们令\(g\)为\(1\)即可。
\[S(n)=\sum\limits_{i=1}^n\sum\limits_{d|i}\varphi(d)-\sum\limits_{i=1}^n\sum\limits_{d|i,d\neq i}\varphi(d)\\ = \sum\limits_{i=1}^ni-\sum\limits_{i=1}^n\sum\limits_{d|i,d\neq 1}\varphi(\frac{n}{d})\\ =\frac{n(n+1)}{2}-\sum\limits_{d=2}^n\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\varphi(i)\\ = \frac{n(n+1}{2}-\sum\limits_{d=2}^nS(\lfloor\frac{n}{d}\rfloor) \]
再来推一下\(\mu\)的前缀和。因为\(\mu * 1= \epsilon\),\(1\)和\(\epsilon\)的前缀和都非常好求,所以还是令\(g=1\)
\[S(n)=\sum\limits_{i=1}^n\sum\limits_{d|i}\mu(d)-\sum\limits_{i=1}^n\sum\limits_{d|i,d\neq i}\mu(d)\\ =1-\sum\limits_{d=2}^n\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\mu(i)\\ = 1-\sum\limits_{d=2}^nS(\lfloor\frac{n}{d}\rfloor) \]
关于预处理
这样直接搜的复杂度是\(O(n^{\frac{3}{4}})\),为了使复杂度更优,我们需要先预处理出一部分答案,如果我们预处理除了\([1,K]\)的答案,当计算\([1,K]\)中的结果时,直接返回即可。
可以证明当\(K\)取\(n^{\frac{2}{3}}\)时,复杂度最优秀为\(n^{\frac{2}{3}}\)
关于记忆化
为了让复杂度是正确的,我们肯定要将每次算出的结果记忆化下来。因为\(n\)比较大,所以需要用\(map\)来记忆化。这样复杂度就会多个\(log\)
还有一种方法,因为我们每次递归到的\(x\)肯定满足:所有满足\(\frac{n}{y}=\frac{n}{x}\)的\(y\)中,只有\(x\)会被计算到。所以我们可以用一个数组ma记忆化,当查询的\(n\)小于等于\(K\)时,我们直接范围答案,当查询的\(n\)大于\(K\)时,我们查看\(ma[\lfloor \frac{n}{K}\rfloor]\)中的值即可。
当有多次询问时,第二种方法需要清空,复杂度可能不如第一种。
代码
以求\(\varphi\)的前缀和为例。
void pre() { phi[1] = 1; for(int i = 2;i < N;++i) { if(!vis[i]) { prime[++tot] = i; phi[i] = i - 1; } for(int j = 1;j <= tot && prime[j] * i < N;++j) { vis[prime[j] * i] = 1; if(i % prime[j]) { phi[i * prime[j]] = phi[i] * (prime[j] - 1); } else { phi[i * prime[j]] = phi[i] * prime[j]; break; } } } for(int i = 2;i < N;++i) phi[i] = (phi[i] + phi[i - 1]) % mod; } ll MAX; ll PHI(ll n) { if(n < N) return phi ; if(vis[MAX / n]) return maphi[MAX / n]; vis[MAX / n] = 1; ll ret = (n % mod) * ((n + 1) % mod) % mod * inv % mod; for(ll l = 2,r;l <= n;l = r + 1) { r = n / (n / l); ret -= ((r - l + 1) % mod) * PHI(n / l) % mod; ret %= mod; } return maphi[MAX / n] = ret; }
- 使用MPI并行求解前缀和(prefix sum)
- poj3295(前缀表达式的运用和递归求解表达式)解题报告
- C++:求解给定字符串的前缀
- 求解后缀数组的最长前缀
- java求解最长公前缀
- ACMjava求解最大连续和的三种方法 暴力枚举,S前缀,回溯法
- HDU 6153 A Secret 套路,求解前缀在本串中出现的次数
- PTA 求解给定字符串的前缀
- 分治法求解-最长公共前缀
- (字符串的模式匹配4.7.19——前缀数组suffix的应用)POJ 2752 Seek the Name, Seek the Fame(求解一个字符串中前缀和后缀一样的位置)
- python 实现求解字符串集的最长公共前缀方法
- KMP算法---核心就是NEXT数组求解---最长真后缀与前缀相同的字符数
- 栈和队列(6)--用栈来求解汉诺塔问题①
- 2018年华为软件精英挑战赛求解思路
- 最短路径实现代码-->Dijkstra求解最短路径问题
- hdu 5468 Puzzled Elena(前缀性质+dfs序+容斥)
- 快速幂求解
- 约瑟夫环问题求解(循环链表法)
- 三分法(Ternary Search)求解凸(凹)函数的极值问题<题目篇>
- 每次询问求出两个字符串的最长公共前缀的长度 后缀数组+RMQ+lcp UVA 12338 - Anti-Rhyme Pairs