【知识总结】线性筛_杜教筛_Min25筛
首先感谢又强又嘴又可爱脸还筋道的国家集训队(Upd: WC2019 进候选队,CTS2019 不幸 rk6 退队)神仙瓜 ( jumpmelon ) 给我讲解这三种筛法~~
由于博主的鸽子属性,这篇博客可能会无限期咕咕咕
线性筛
这种算法是比较基础的筛法,在入门时就已经学习用它来筛一定范围内的质数了,因此具体算法流程无需赘述。但在筛质数的基础上,这种算法由于其优越性质在处理数论函数时也被广泛应用。这里直接给出筛出小于 \(N\) 的质数的模板。
void init()
{
for (int i = 2; i < N; i++)
{
if (!mark[i])
prime[cnt++] = i;
for (int j = 0; j < cnt && (ll)i * prime[j] < N; j++)
{
int k = i * prime[j];
mark[k] = true;
if (i % prime[j] == 0)
break;
}
}
}
知其然也要知其所以然,不能像我一样一年线性筛都是背模板,前几天才仔细想过原理qwq。筛去每个合数的一定是它的最小质因子。由于最小质因子是唯一的,所以每个合数只会被筛去一次,这就保证了线性的复杂度。考虑对于有超过一个质因子的合数 \(T\) ,它的最小质因子是 \(p\) ,任一非最小质因子是 \(p'\) ,那么 \(\frac{T}{p'}\) 一定含有质因子 \(p\) 。因此当上述代码中 \(i=\frac{T}{p'}\) 时,\(prime[j]=p\) 时就会退出循环,无法循环到 \(prime[j]=p'\) 的情况。因此上述结论成立。
上面提到过,线性筛经常被用于处理一些数论函数,尤其常见于积性函数(非积性函数能不能搞我不知道,反正我没见过)。下面的代码处理了莫比乌斯函数 \(\mu(x)\) 和欧拉函数 \(\varphi(x)\) 。这段代码是我现写的,不知道有没有锅……
void init()
{
mu[1] = phi[1] = 1;
for (int i = 2; i < N; i++)
{
if (!mark[i])
prime[cnt++] = i, mu[i] = -1, phi[i] = i - 1;
for (int j = 0; j < cnt && (ll)i * prime[j] < N; j++)
{
int k = i * prime[j];
mark[k] = true;
if (i % prime[j] == 0)
{
mu[k] = 0;
phi[k] = phi[i] * prime[j];
break;
}
else
{
mu[k] = -mu[i];
phi[k] = phi[i] * phi[prime[i]];
}
}
}
}
首先,由于 \(\mu\) 和 \(\varphi\) 都是积性函数,所以当 \(i\) 和 \(prime[j]\) 互质时直接把它们的函数值乘起来即可。
先看 \(\mu\) 的部分。根据定义,当 \(x\) 是质数时 \(\mu(x)=-1\) ;当 \(x\) 是某质数平方的倍数时,\(\mu(x)=0\) 。由于当 \(i\equiv 0 \mod prime[j]\) 时 \(k\) 是 \(prime[j]^2\) 的倍数,所以 \(mu[k]=0\) 。
再看 \(\varphi\) 的部分。当 \(i\equiv 0 \mod prime[j]\) 时,考虑欧拉函数的公式 \(\varphi(x)=x\prod (1-\frac{1}{p_i})\) (其中 \(p_i\) 取遍 \(x\) 的所有质因子) ,发现求积符号里面没有变化(没有新增质因子),只是 \(x\) 变成了 \(x\cdot p\) ,所以 \(phi[k]=phi[i]*prime[j]\) 。
这两个常见的例子启示我们:想用线性筛处理积性函数 \(f(x)\) ,就要解决 \(x\) 是质数的情况和已知 \(f(i)\) 求 \(f(i\cdot p)\) ,其中 \(p\) 是 \(i\) 的质因子的情况(\(i\) 与 \(p\) 互质的情况直接积性函数性质 \(f(i\cdot p)=f(i)\cdot f(p)\) 就好了)。这样的用例如 【洛谷2257/BZOJ2820】YY的GCD(数论/莫比乌斯函数) (我远古时期的博客……)。
然鹅事实上很多情况下上述后一种情况的方案并不是很好构造……于是这里有一个比较通用的方法:设 \(last[i]\) 表示 \(i\) 的最小质因子 \(p\) 的一个幂 \(p^k\) ,使 \(p^k\) 能整除 \(i\) 且 \(k\) 最大(这个表述不太友好,但是我想不出来友好的表述qwq)。那么处理 \(last[i]\) 的方法显而易见:
void init()
{
for (int i = 2; i < N; i++)
{
if (!mark[i])
prime[cnt++] = last[i] = i;
for (int j = 0; j < cnt && (ll)i * prime[j] < N; j++)
{
int k = i * prime[j];
mark[k] = true;
if (i % prime[j] == 0)
{
last[k] = last[i] * prime[j];
break;
}
else
last[k] = prime[j];
}
}
}
此时,当\(i\equiv 0 \mod prime[j]\) 时,我们也有了一个漂亮的利用积性函数性质而不必特殊构造的计算方法:\(f[k]=f[\frac{i}{last[i]}]\cdot f[last[k]]\) ,只是……
如果 \(k\) 是质数的幂导致 \(last[k]=k\) 怎么办啊!!!
对于具体题目手动构造吧,通常都是很好构造的……
下面举个例子:函数 \(f(x)=\sum_{d|n} d^K\cdot \mu(\frac{x}{d})\) ,其中 \(K\) 是给定的常量(大写是为了方便叙述,防止与代码中的 \(k\) 混淆)。(这个例子出自【BZOJ4407】于神之怒加强版)
当 \(k=p^a\) ,由于当 \(\frac{k}{d}\) 有质数平方因子时 \(\mu(\frac{k}{d})=0\) ,所以只有 \(d=p^a=k\) 和 \(d=p^{a-1}\) 两项对答案有贡献,于是就有 \(f(k)=k^K-p^{(a-1)K}\) 。(很多和 \(\mu\) 有关的函数都可以利用有平方因子的项函数值为 \(0\) 的性质来优化到只有很少的项),代码如下:
void init()
{
f[1] = 1;
for (int i = 2; i < N; i++)
{
if (!mark[i])
prime[cnt++] = last[i] = i, f[i] = (power(i, K) - 1 + p) % p;
for (int j = 0; j < cnt && (ll)i * prime[j] < N; j++)
{
int k = i * prime[j];
mark[k] = true;
if (i % prime[j] == 0)
{
last[k] = last[i] * prime[j];
if (k != last[k])
f[k] = (ll)f[i / last[i]] * f[last[k]] % p;
else
f[k] = (power(k, K) - power(i, K) + p) % p;
break;
}
else
{
last[k] = prime[j];
f[k] = (ll)f[i] * f[prime[j]] % p;
}
}
}
}
杜教筛
杜教筛用于在低于线性的时间(据说是 \(O(n^{\frac{2}{3}})\) ?并不会证)内算出指定积性函数 \(f(x)\) 的前缀和 \(S_f(x)\) 。
(以下对于函数 \(f\) ,定义 \(S_f(x)=\sum_{i=1}^{x}f(x)\) )
杜教筛的主要思想是构造便于计算前缀和的两个函数 \(g(x)\) 和 \(h(x)=(f*g)(x)\) ,然后通过 \(S_g(x)\) 和 \(S_h(x)\) 计算出 \(S_f(x)\) 。具体如下(第三行是先枚举 \(d\) ,然后枚举 \(d\) 的所有倍数,用 \(i\cdot d\) 代替第二行中的 \(i\) ):
h(n)&=\sum_{d|n}g(d)f(\frac{n}{d})\\
S_h(n)&=\sum_{i=1}^{n}\sum_{d|i}g(d)f(\frac{n}{d})\\
S_h(n)&=\sum_{d=1}^{n}\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}g(d)f(i)\\
S_h(n)&=\sum_{d=1}^{n}g(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i)\\
S_h(n)&=\sum_{d=1}^{n}g(d)S_f(\lfloor\frac{n}{d}\rfloor)\\
S_h(n)&=g(1)S_f(n)+\sum_{d=2}^{n}g(d)S_f(\lfloor\frac{n}{d}\rfloor)\\
S_f(n)&=\frac{S_h(n)-\sum_{d=2}^{n}g(d)S_f(\lfloor\frac{n}{d}\rfloor)}{g(1)}
\end{aligned}
\]
之前提到过,构造的 \(S_g(x)\) 和 \(S_h(x)\) 都便于计算(通常是 \(O(1)\) ),所以右边明显可以数论分块,递归计算 \(S_f(\lfloor\frac{n}{d}\rfloor)\) 。
那么现在的问题就是构造合适的函数 \(g\) 。首先,杜教筛经常用来筛 \(\mu\) 和 \(\varphi\) ,而这两个函数都有优美的性质使 \(g\) 的选取十分容易。下面三个式子非常有用,请牢记。我不想证也不会证
(其中 \(\epsilon(x)=\begin{cases}1\ (x=1)\\0\ \mathrm{otherwise}\end{cases}\) , \(\mathrm{id(x)}_k=x^k\) , \(1(x)=1\) )
\mu*1&=\epsilon\\
\varphi*1&=\mathrm{id}_1\\
\mu*\mathrm{id}_1&=\varphi\\
\end{aligned}
\]
很显然 \(\epsilon\) 的前缀和恒为 \(1\) ,\(1\) (常函数)的前缀和就是 \(\mathrm{id}\) ,\(\mathrm{id}_1\) 的前缀和就是一个等差数列求和(顺带一提,\(S_{\mathrm{id}_2}(n)=\frac{n(n+1)(2n+1)}{6}\) ),都很好算。所以要求的 \(f=\mu\) 和 \(\varphi\) 时,\(g\) 都选 \(1\) ,对应 \(h\) 分别是 \(\epsilon\) 和 \(\mathrm{id}_1\) 。
其他的构造技巧暂且不提,先看看筛 \(\mu\) 和 \(\varphi\) 函数的代码实现:
const int N067 = 1.67e6 + 10, N033 = 1.3e3 + 10;
typedef long long ll;
ll sumphi[N033], prephi[N067];
int summu[N033], premu[N067], phi[N067], mu[N067], prime[N067], cnt;
bool mark[N067], vis[N033];
void init()
{
phi[1] = mu[1] = 1;
for (int i = 2; i < N067; i++)
{
if (!mark[i])
prime[cnt++] = i, phi[i] = i - 1, mu[i] = -1;
for (int j = 0; j < cnt && (ll)i * prime[j] < N067; j++)
{
int k = i * prime[j];
mark[k] = true;
if (i % prime[j] == 0)
{
mu[k] = 0;
phi[k] = phi[i] * prime[j];
break;
}
else
{
mu[k] = -mu[i];
phi[k] = phi[i] * phi[prime[j]];
}
}
}
prephi[0] = premu[0] = 0;
for (int i = 1; i < N067; i++)
{
prephi[i] = prephi[i - 1] + phi[i];
premu[i] = premu[i - 1] + mu[i];
}
}
typedef pair<ll, int> pli;
pli Du_Algorithm(const int n, const int x)
{//f(i) = phi(i), g(i) = 1, h(i) = i;
//f(i) = mu(i), g(i) = 1, h(i) = \epsilon(i)
if (x < N067)
return make_pair(prephi[x], premu[x]);
int t = n / x;
if (vis[t])
return make_pair(sumphi[t], summu[t]);
pli ans = make_pair((ll)(x + 1) * x / 2, 1);
int pos = 2;
while (pos <= x)
{
int tmp = x / (x / pos);
pli anss = Du_Algorithm(n, x / tmp);
ans.first -= anss.first * (tmp - pos + 1);
ans.second -= anss.second * (tmp - pos + 1);
pos = tmp + 1;
}
vis[t] = true;
return make_pair(sumphi[t] = ans.first, summu[t] = ans.second);
}
(以下这些东西怎么影响复杂度我都不会证)
首先对于 \(n^\frac{2}{3}\) 以内的可以直接线性筛预处理。而对于大于 \(n^\frac{2}{3}\) 的情况,一定要记忆化来保证复杂度。记忆化可以使用 STL 的 map ,也可以把 \(x\) 映射到 \(\lfloor\frac{n}{x}\rfloor\) 。这样做的理由是 \(x\) 只会是 \(n\) 除以某个数下取整的结果,而当 \(x\) 大于 \(n^\frac{2}{3}\) 时一个 \(\lfloor\frac{n}{x}\rfloor\) 只对应一个 \(x\) (并不会证明),所以这样记忆化是对的。
下面介绍一个比较一般的构造 \(g\) 函数的技巧:
首先要知道,狄利克雷卷积有交换律和结合律。它一个重要的性质,类似乘法分配律(注意 \(h\) 必须是完全积性函数):
\]
利用这个性质可以「凑」出一些有用的东西。比如一个看起来很棘手的积性函数 \(f=id_2\cdot(id_5*\mu)\) 。似乎一时想不出什么合适的 \(g\) ,但是……看到有个 \(\mu\) ?
看到 \(\mu\) 自然想到要搞个 \(1\) 来合作出一个 \(\epsilon\) 啊。于是根据上面那个性质,卷上 \(g=id_2\cdot 1\) :
\]
这样就可以把 \(id_2\) 提出来,得到(别忘了狄利克雷卷积有交换律和结合律):
\]
点乘右边就是 \(id_5*\epsilon\) ,手玩一下发现这玩意就是 \(id_5\) (任何函数乘上单位函数 \(\epsilon\) 等于本身)。
于是:
\]
至于 \(id_7\) 的前缀和怎么算?上面那个函数是我瞎写的,我也不知道最后会算出来这种奇怪的东西。所以这玩意的前缀和我也不会算,我去问蹦蹦蹦蹦瓜( jumpmelon )了,再见。
Upd: 蹦蹦瓜说 \(id_k\) 的前缀和是一个 \(k+1\) 次多项式,所以暴力算若干项然后 高斯插值 高斯消元算一下就好了。
Min_25 筛
咕了快三个月后开始更新
这玩意我学了三遍,写博客的时候在 APIO2019 讲课现场掉线后学第四遍(摔.jpg)
注意 Min_25 筛只能求积性函数前缀和。定义 \(p_i\) 表示从小到大第 \(i\) 个质数。特别地, \(p_0=1\) 。
记 \(g(n,m)\) 为 \(\sum f(i)\) ,其中 \(i\) 满足 \(2\leq i\leq n\) 且 \(i\) 的最小质因子大于 \(p_m\) 。则答案为 \(g(n,0)+f(1)=g(n,0)+1\) 。出于某种原因,再设 \(h(n)\) 表示 \(\sum f(p)\) ,其中 \(p\leq n\) 且 \(p\) 是质数(怎么算下来再说)。则:
(p_i^k)\cdot \left([k>1]+g\left(\lfloor \frac{n}{p_i^k}\rfloor,i\right)\right)\right)+h(n)-h(p_m)\]
(我也不知道怎么就套了这么多括号)方括号表示其中的表达式为真则值为 \(1\) ,否则为 \(0\) 。
这个式子的主要思想是:对于符合条件的质数(即大于 \(p_m\) 的质数)单独提出来算(即 \(h(n)-h(p_m)\) );对于合数,枚举它的最小质因子 \(p_i\) 及其指数 \(k\) ,利用积性函数的性质将其拆成 \(p_i^k\cdot a\) 的形式,其中 \(a\) 的最小质因子大于 \(p_i\) 且 \(1\leq a \leq \lfloor\frac{n}{p_i^k}\rfloor\) 。\(a=1\) 时直接算(注意 \(k=1\) 时是质数,在 \(h(n)\) 里算过了),否则递归下去。
接下来的问题是怎么算 \(h(n)\) 。构造 完全积性函数 \(f'(n)\) ,使得当 \(p\) 是质数 时 \(f'(p)=f(p)\) (怎么构造稍后再说)。再设一个函数 \(z(n,m)\) 表示 \(\sum f'(i)\) ,其中 \(i\leq n\) 且 \(i\) 的最小质因子大于 \(p_m\) 或 \(i\) 是质数,则 \(h(n)=z(n,\omega(\sqrt{n}))\) ,其中 \(\omega(\sqrt{n})\) 表示不超过 \(\sqrt{n}\) 的质数个数(毕竟对于质数来说 \(f\) 和 \(f'\) 是一回事,最小质因子超过 \(\sqrt{n}\) 的只有质数了)。考虑从 \(m-1\) 递推出 \(m\) ,即从 \(z(n,m-1)\) 中减去最小质因子为 \(p_m\) 的数的贡献:
\]
注意,根据定义,\(z\left(\lfloor \frac{n}{p_m}\rfloor,m-1\right)\) 中不仅包含(我们需要的)不超过 \(\lfloor \frac{n}{p_m}\rfloor\) 且最小质因子大于 \(p_{m-1}\) 的数的贡献,还包括所有不超过 \(\lfloor \frac{n}{p_m}\rfloor\) 的质数的贡献。在第二部分中, \(\left[p_m,\lfloor\frac{n}{p_m}\rfloor\right]\) 中的质数也满足第一个条件(即是两部分的交集),所以无需减去。要减去的是小于 \(p_m\) 的质数的贡献,即 \(z(p_m-1,m-1)\) 。
现在只剩下最后一个问题了:如何构造 完全积性函数 \(f'(n)\) 。通常来说,当 \(p\) 是质数时 \(f(p)\) 是关于 \(p\) 的多项式,如 \(\mu(p)=-1\),\(\varphi(p)=p-1\) 等。而 \(id_k\) ( \(k\in N\) , \(id_k(n)=n^k\) ) 都是完全积性函数,所以可以分别对每项用不同的 \(id_k\) 作为 \(f'\) 求出 \(h\) ,然后乘上对应系数加起来即可。以 \(\varphi\) 为例,可以用 \(f'_1(i)=i\) 算出 \(z_1\) ,用 \(f'_2(i)=1\) 算出 \(z_2\) ,最后 \(z=z_1+(-1)\cdot z_2\) 。
【知识总结】线性筛_杜教筛_Min25筛的更多相关文章
- BZOJ_4176_Lucas的数论_杜教筛+莫比乌斯反演
BZOJ_4176_Lucas的数论_杜教筛+莫比乌斯反演 Description 去年的Lucas非常喜欢数论题,但是一年以后的Lucas却不那么喜欢了. 在整理以前的试题时,发现了这样一道题目“求 ...
- Min_25 筛与杜教筛
杜教筛 \(\) 是 \(\) 的前缀和,\(\), \(\) 同理. 假设 \( × = ℎ\) ,并且 \(, \) 易求出,\(\) 难求出. 那么 \[H () = \sum_{ \cdot ...
- LOJ# 572. 「LibreOJ Round #11」Misaka Network 与求和(min25筛,杜教筛,莫比乌斯反演)
题意 求 \[ \sum_{i = 1}^{n} \sum_{i = 1}^{n} f(\gcd(i, j))^k \pmod {2^{32}} \] 其中 \(f(x)\) 为 \(x\) 的次大质 ...
- P4213 【模板】杜教筛(杜教筛)题解
题意: 求\(\sum_{i=1}^n\varphi(i)\)和\(\sum_{i=1}^n\mu(i)\) 思路: 由性质可知:\(\mu*I=\epsilon,\varphi*I=id\)那么可得 ...
- 莫比乌斯反演/线性筛/积性函数/杜教筛/min25筛 学习笔记
最近重新系统地学了下这几个知识点,以前没发现他们的联系,这次总结一下. 莫比乌斯反演入门:https://blog.csdn.net/litble/article/details/72804050 线 ...
- LG4213 【模板】杜教筛(Sum)和 BZOJ4916 神犇和蒟蒻
P4213 [模板]杜教筛(Sum) 题目描述 给定一个正整数$N(N\le2^{31}-1)$ 求 $$ans_1=\sum_{i=1}^n\varphi(i)$$ $$ans_2=\sum_{i= ...
- hihocoder #1456 : Rikka with Lattice(杜教筛)
hihocoder #1456 : Rikka with Lattice(杜教筛) 题意 : 给你一个\(n*m\)方格图,统计上面有多少个格点三角形,除了三个顶点,不覆盖其他的格点(包括边和内部). ...
- 杜教筛 && bzoj3944 Sum
Description Input 一共T+1行 第1行为数据组数T(T<=10) 第2~T+1行每行一个非负整数N,代表一组询问 Output 一共T行,每行两个用空格分隔的数ans1,ans ...
- BZOJ3944: Sum(杜教筛模板)
BZOJ3944: Sum(杜教筛模板) 题面描述 传送门 题目分析 求\(\sum_{i=1}^{n}\mu(i)\)和\(\sum_{i=1}^{n}\varphi(i)\) 数据范围线性不可做. ...
随机推荐
- java 十三周总结
- 在JQuery中$(document.body)和这个$("body") 这两的区别在哪里?
两种写法代表的是同一个对象 $("body") 是一个选择器,jQuery 会从 DOM 顶端开始搜索,直到找到标签为 body 的元素. 而 $(document.body) 中 ...
- 利用Clojure统计代码文件数量和代码行数
;; 引入clojure的io包 (use '[clojure.java.io]) ;; 遍历目录将所有符合要求的文件做为列表返回 (defn walk [dirpath pattern] (doal ...
- PHP中错误与异常的日志记录用法分析
原文:http://www.jb51.net/article/89548.htm ----------------------------------------------------------- ...
- duilib中加入自己定义控件之后怎么可以在xml文件里配置使用
加入自己定义控件可能有两种不同的情况: 1. 在duilib库中加入的自己定义控件. 2. 在我们的应用程序中自己重写了一个控件. 以下開始解说不同的情况下怎么才干支持在xml文件配置控件: 1. ...
- Google2015校招在线測试题1----扫雷最少点击次数
Problem Minesweeper is a computer game that became popular in the 1980s, and is still included in so ...
- LeetCode 541. Reverse String II (反转字符串 II)
Given a string and an integer k, you need to reverse the first k characters for every 2k characters ...
- LeetCode 171. Excel Sheet Column Number (Excel 表格列数字)
Related to question Excel Sheet Column Title Given a column title as appear in an Excel sheet, retur ...
- Android获取全部存储卡挂载路径
近期因项目需求.须要在存储卡查找文件,经測试发现部分手机挂载路径查找不到,这里分享一个有效的方法. /** * 获取全部存储卡挂载路径 * @return */ public static List& ...
- UVA - 10324 Zeros and Ones
Description Given a string of 0's and 1's up to 1000000 characters long and indices i and j, you are ...