[算法复习] 素数判定:欧拉筛 Miller_Rabin
摘取了以前的笔记并做了一些补充说明
9,素数判定
O(n) + O(1): 线性筛.
又叫欧拉筛。
先考虑一种朴素的想法。
对于每一个数\(k\), 我们枚举\(i\),将\(ik\)标记为合数。
因为每个数都可以分解成若干个素数的积,我们只需枚举每个素数作为\(k\)即可。
但是复杂度依然很高,我们考虑优化。
对于每个数,仅用它最小的质因数来筛它。
我们用一个数组存储质数,枚举\(i\)作为它的倍数,并且每次都判断一下如果\(i\)是素数,则将其加入素数数组。
然后我们从小到大枚举\(pri[j]\)用其筛除\(i * pri[j]\).
特别的,如果\(!(i \ mod \ pri[j])\) 那么我们筛完就break.
下面我们来证明这个算法的正确性和复杂度。
正确性:
接下来我们证,如果\(!(i \ mod \ pri[j])\),那么对于后面的\(i \cdot pri[j+k]\)来说,用\(pri[j+k]\)来筛一定是不优的,也就是这个剪枝肯定不会导致漏筛。
因为\(!(i \ mod \ pri[j])\)所以我们可以设\(i = t \cdot pri[j]\)
那么\(i \cdot pri[j+k] = t \cdot pri[j] \cdot pri[j+k]\)
又知\(pri[j+k] > pri[j]\),也就是说,用\(pri[j+k]\)来筛,不如等到\(i = t \cdot pri[j+k]\)的时候用\(pri[j]\)来筛。
所以这个剪枝肯定是合法的。
复杂度:
那么为什么每个数仅被最小的质因数筛除了呢?
接下来我们证,这个剪枝可以导致每个数仅被最小的质因数筛除.
\(pm[m]\)为\(m\)的质因数个数,对于一个数\(m\)设\(m = \prod_{i = 1}^{pm[m]}p_i\),其中\(p\)序列为升序排列,若\(m\)不是被\(p_1\)筛除了,我们假设这个数是\(p_t\)。
我们先考虑\(m\)质因数大于\(2\)个的情况:
那么筛掉\(m\)时我们枚举到的\(i = \prod_{i = 1}^{pm[m]}(p_i) / p_t = b \cdot p_1\),\(b\)为某个不为\(1\)正整数。
此时,\(i\)会在枚举到\(p_1\)的时候停下,也就是筛完\(b \cdot p_1 \cdot p_1\)就不筛了,也就不会筛\(m = b \cdot p_1 \cdot p_t\)了。与假设矛盾。
再考虑\(m\)刚好有两个质因数的情况,此时\(m = p_1 \cdot p_2\),那么根据假设,\(m\)被\(p_2\)筛掉了。
也就是在\(i = p_1\)的时候\(m\)被筛了。
这显然是不合法的,因为此时\(p_2\)这个质数还没被加入素数数组。
所以\(m\)只可能在\(i = p_2\)时被\(p_1\)筛掉。与假设矛盾。
综上,假设情况是不可能的,\(m\)必然会被其最小质因数筛除.
又因为在第二个循环中,每循环一次必筛除一个数,而每个数只会被筛走一次,因此第二个循环的总次数为被筛除的素数个数。所以复杂度是\(O(n)\)
代码:
void get()
{
z[1] = 1;
for(R i = 2; i <= n; i ++)
{
if(!z[i]) pri[++ tot] = i;
//printf("%d\n", tot);
for(R j = 1; j <= tot && 1LL * i * pri[j] <= n; j ++)
{
z[i * pri[j]] = 1;
if(!(i % pri[j])) break;
}
}
}
O(\(\sqrt{n}\)): 暴力枚举\(\sqrt{n}\)以内的因子
Miller_Rabin算法:(这部分写的好烂,哪天我重学Miller_Rabin再来更新吧)
根据费马小定理有\(a^{p - 1} \equiv 1 (mod \quad p)\),要求p是质数.
那么如果对于等式\(a^{p - 1} \equiv 1 (mod \quad p)\),枚举多个a,均满足等式,那么可以认为当前的p有很大概率是素数。
但是依然有很大概率不是,,,因此这个时候采取二次探测来减小错误概率。
首先有定理:若\(a^2 \equiv 1 (mod \quad p)\)且p为质数,那么a = 1 或 p - 1.
证明如下:若等式成立,则\(a^{2} - 1 \equiv 0 (mod \quad p)\).
即\((a + 1)(a - 1) \equiv 0 (mod \quad p)\).
因此要么\(a + 1 = p\) ---> \(a = p - 1\).
或者\(a - 1 = 0\) ---> \(a = 1\).
因为这个探测是在满足费马测试的情况下才进行的,因此对于某个已经被枚举过的a,我们已经有\(a^{p - 1} \equiv 1 (mod \quad p)\).我们设当前指数\(x = p - 1\)
当p不为2且为素数时,\(p\)一定为奇数,即\(p - 1\)一定为偶数.
因此我们可以把原式看做\(a^{\frac{x}{2}} \cdot a^{\frac{x}{2}} \equiv 1 (mod \quad p)\),其中可以设\(t = a^{\frac{x}{2}}\)。
那么原式就是\(t \cdot t \equiv 1(mod \quad p)\)
那么因为这个是直接根据原式化过来的,因此我们已经有它同余1了,因此只需要再判断t是否等于1 或 p - 1即可。
如果t = 1 且 x为偶数,那么我们就又得到了一个新的式子\(a^{\frac{x}{2}} \equiv 1 (mod \quad p)\)
此时因为x为偶数,所以我们依然这个把这个新式子当做上面的式子重新做探测,直到不满足t = 1且x为偶数为止。