定义
设整数 。如果
p除了平凡约数外没有其他约数,那么称p 为
素数 (不可约数 )。
若整数 。且
a不是素数,则称a为 合数 。
若整数的因数是素数,则该素数称为该整数的素因数
性质
大于 1 的整数 a是合数,等价于存在整数
(d,e)((1<d,e<a))使得
若素数 p有大于 1 的约数 (d),则 (即素数只有 1 和它自身为约数)
任意大于 1 的整数 a都可以表示为素数的乘积:
对于合数 a,一定存在素数 使得
素数有无穷多个。
所有大于 3 的素数都可以表示为
判断质数
较小数据范围
从第一个质数2开始验证,一个个验证待测数n是否能被每个数整除,若任何一个可以整除n,则不是质数
注意,只需要验证到 即可
因为若一个数x已经验证无法整除n,则对应的 之后的数无需验证,因为此时这之后的因子与x相乘的结果一定大于n,一直到 , ,故该后面的都不需要验证
1 2 3 4 5 6 7 bool isPrime (long long n) { if (n<=1 ) return false ; for (long long i = 2 ; i*i<=n;i++){ if (n%i==0 ) return false ; } return true ; }
较大数据范围
对于较大的数字,可以使用Miller-Rabin测试
过程:
每次选择1~n-1上的随机数字,或者指定一个比n小的质数进行测试
经s次(一般20次以内)得到,s越出错概率越低,速度越慢
对于Java用户,其BigInteger类型内自带了一个判断质数的方法isProbablePrime(s),使用的测试方法就包含了Miller-Rabin测试,对于大于1e9的数据也不会出现溢出
对于CPP用户,需要使用__int128
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 60 61 62 #include <bits/stdc++.h> #define endl '\n' using namespace std;typedef __int128 ll;typedef pair<int ,int > PII;const int N = 2e5 + 10 ;const int mod = 1e9 + 7 ;ll n; template <typename T> inline T read () { T x = 0 , f = 1 ; char ch = 0 ; for (; !isdigit (ch); ch = getchar ()) if (ch == '-' ) f = -1 ; for (; isdigit (ch); ch = getchar ()) x = (x << 3 ) + (x << 1 ) + (ch - '0' ); return x * f; } template <typename T> inline void write (T x) { if (x < 0 ) putchar ('-' ), x = -x; if (x > 9 ) write (x / 10 ); putchar (x % 10 + '0' ); } ll qpow (ll a, ll n, ll mod) { ll re = 1 ; while (n){ if (n&1 ) re = (re*a)%mod; a = (a*a)%mod; n>>=1 ; } return re%mod; } vector<ll> p ={2 ,3 ,5 ,7 ,11 ,13 ,17 ,19 ,23 ,29 ,31 ,37 }; bool miller_rabin (ll n) { if (n<3 ||n%2 ==0 ) return n==2 ; ll u = n-1 , t = 0 ; while (u%2 ==0 ) u/=2 , t++; for (auto a:p){ if (n==a) return 1 ; if (n%a==0 ) return 0 ; ll v = qpow (a, u, n); if (v==1 ) continue ; ll s = 1 ; for (; s<=t;s++){ if (v==n-1 ) break ; v = (v*v)%n; } if (s>t) return 0 ; } return 1 ; } void solve () { n = read <ll>(); if (miller_rabin (n)) puts ("Yes" ); else puts ("No" ); } int main () { ios::sync_with_stdio (0 ); cin.tie (0 ); cout.tie (0 ); ll tomorin=read <ll>(); while (tomorin--) solve (); return 0 ; }
质因数分解
对于数n,因数是成对分布的,故可以枚举每一个数i到 ,若是合数说明i是他的一个质因子,让 ,直到无法再除,证明将当前质因子对应的因数除尽,再试下一个i
1 2 3 4 5 6 7 8 9 void f (int n) { for (int i = 2 ; i*i<=n;i++){ if (n%i==0 ){ cout << i << endl; while (n%i==0 ) n/=i; } } if (n>1 ) cout << n << endl; }
注意,若已经有一个素数表,时间复杂度将会从 下降为
因数分解
进一步的,我们可以进行因数分解
1 2 3 4 5 6 7 8 9 10 11 void f (int n) { for (int i = 2 ; i*i<=n; i++) { if (n%i==0 ) { cout << i << endl; if (i!=n/i) cout<< n/i << endl; } } }
这样的时间复杂度仍为
质数筛
埃氏筛
用一个vis数组标记是质数还是合数,默认都是质数
从第一个质数2开始,计算 , ,这些数肯定是合数,将这些数对应的vis标记为合数,再遍历操作下一个质数
注意:每次标记只需要从质数 开始往后每次加 即可 ,因为前面的肯定被小于该数的质数标记过了
另外,由于偶数除了2都是合数,我们可以选择跳过偶数来让内存和操作需求都减半
另另外,由于bitset的连续读写效率,我们可以将bool数组改为bitset来将布尔值压到一个bit中,可以显著减少内存消耗,这种方法称为位级压缩
时间复杂度:
1 2 3 4 5 6 7 8 9 10 vector<bool > prime (N, true ) ;void ehrlich (int n) { for (int i = 2 ;i*i<=n;i++){ if (prime[i]){ for (int j = i*i;j<=n;j+=i){ prime[j]=false ; } } } }
若只是计数,埃氏筛还可以改进:我们可以预估质数数量,若发现更多合数就减掉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 vector<bool > prime (N, false ) ;int ehrlich (int n) { int cnt = (n+1 )/2 ; for (int i = 3 ;i*i<=n;i+=2 ){ if (!prime[i]){ for (int j = i*i;j<=n;j+=2 *i){ if (!prime[j]){ prime[j]=true ; cnt--; } } } } }
欧拉筛
埃氏筛也有重复筛的情况,因此会慢一些
而欧拉筛保证了每个合数只被自己的最小质因子筛掉
时间复杂度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 vector<bool > prime (N, true ) ;void eular (int n) { int tprime[n/2 +1 ]; int cnt = 0 ; for (int i = 2 ;i<=n;i++){ if (prime[i]){ tprime[cnt++]=i; } for (int j = 0 ; j<cnt;j++){ if (i*tprime[j]>n)break ; prime[i*tprime[j]]=false ; if (i%tprime[j]==0 ) break ; } } }
分段筛法
普通的线性筛在数据范围很大的时候会超时或耗尽内存,此时我们需要使用分段筛
思想:用普通筛筛出 内的所有质数,然后将所需区间[l,
r]分成一段或多段,并对段进行标记,用筛出的质数标记段中合数,并统计没被标记的质数即可
步骤: 设右区间为r
计算 ,用筛筛出这之内的数
定义布尔数组标记合数(初始都设为false,表示为可能质数),长度为
对每个质数
若1ll*p*p>r则可以提前break,因为超出区间右端
计算[l,
r]中第一个需要标记的位置start = max(1LL*p*p, ((l + p - 1) / p) * 1LL * p),即从区间第一个p的倍数开始,p小的时候也可以避免把p本身标掉
从start开始以p步长标记start-l位置为合数,数组标记为true
对于l==1时,需要特殊处理数组0下标为合数,因为1不是质数
最后统计数组中false的位置就是质数
注意:
要开longlong,避免乘法溢出
若r和r-l都很大,可以考虑分为多个块,对每个块都进行流程
code例:
洛谷P1835
素数密度
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 vector<bool > prime (N, true ) ;ll tprime[N]; bool vis[N]={};ll cnt = 0 ; void eular (ll n) { for (ll i =2 ;i<=n;i++){ if (prime[i]){ tprime[++cnt] = i; } for (ll j = i+i;j<=cnt;j+=i){ if (i*tprime[j]>n) break ; prime[i*tprime[j]]=false ; if (i%tprime[j]==0 )break ; } } } void solve () { ll l, r; cin >> l >> r; eular (50000 ); if (l==1 ) vis[0 ]=1 ; for (int i = 1 ;i<=cnt;i++){ ll p = tprime[i]; for (ll j = max (2ll , (l-1 )/p+1 )*p;j<=r;j+=p){ if (j-l>=0 ) vis[j-l]=true ; } } ll ans = 0 ; for (ll i = 0 ;i<=r-l;i++)if (!vis[i]) ans++; cout<<ans; }
筛法求约数个数
用 表示 的约数个数, 表示 的最小质因子出现次数
约数个数定理
定理
若 ,则
证明
对每个质因子 ,它的约数有
共
个。由乘法原理可知, 的约数总数就是
实现
由于 是积性函数,所以我们可以使用线性筛
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 vector<int > pri; bool not_prime[N]; int d[N]; int num[N]; void pre (int n) { d[1 ] = 1 ; for (int i = 2 ; i <= n; ++i) { if (!not_prime[i]) { pri.push_back (i); d[i] = 2 ; num[i] = 1 ; } for (int p : pri) { if (i * p > n) break ; not_prime[i * p] = true ; if (i % p == 0 ) { num[i * p] = num[i] + 1 ; d[i * p] = d[i] / num[i] * (num[i] + 1 ); break ; } else { num[i * p] = 1 ; d[i * p] = d[i] * 2 ; } } } }
筛法求约数和
表示 的约数和, 表示 的最小质因子的
用线性筛在 时间内计算出
每个正整数的约数和 ,其中
- ;
- 表示将 分解为最小质因子 的幂乘以其它部分后,单独对这段 求和: 其中 ,且
是 的最小质因子。
借助下面这个分块性质:
若 且 我们可以在线性筛中同时维护 与 。
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 vector<int > pri; bool not_prime[N]; int g[N], f[N]; void pre (int n) { g[1 ] = f[1 ] = 1 ; for (int i = 2 ; i <= n; ++i) { if (!not_prime[i]) { pri.push_back (i); g[i] = i + 1 ; f[i] = i + 1 ; } for (int p : pri) { if (i * p > n) break ; int ip = i * p; not_prime[ip] = true ; if (i % p == 0 ) { g[ip] = g[i] * p + 1 ; f[ip] = f[i] / g[i] * g[ip]; break ; } else { g[ip] = 1 + p; f[ip] = f[i] * f[p]; } } } }