演算法刷題-數論-質數的判定、分解質因數、篩質數
技術標籤:演算法與資料結構
文章目錄
數論
1. 質數
質數:在大於1的整數中,如果只包含1和它本身這兩個約數,那麼這個數就稱為質數。
判斷質數最暴力的寫法,按照質數的定義:看是否有其他的因子。
最樸素的暴力的時間複雜度O(n)
//時間複雜度O(n)
bool isPrime(int n){
if(n<2) return false;
for(int i=2;i<n;i++)
if(n%i==0) return false;
return true;
}
質數的判定—試除法
題目連結:Acwing866. 試除法判定質數
優化:列舉到
n
\sqrt{n}
n
,因為因數都是成對出現的,
如果d是n的因子,那麼n/d也是n的因子,所以只需要列舉一部分就行,列舉哪一部分呢?就是d≤n/d 推出,
d
≤
n
d≤\sqrt{n}
d≤n
這裡 n \sqrt{n} n 的寫法需要注意,這種呼叫數學函式 n \sqrt{n} n 的寫法不好,太慢;另外寫成 i ∗ i ≤ n i*i≤n i∗i≤n存在溢位風險,最好的寫法是 i ≤ n / i i≤n/i i≤n/i
試除法求質數的時間複雜度O(
n
\sqrt{n}
//優化成O(根號n)
bool isPrime(int n){
if(n<2) return false;
for(int i=2;i<=n/i;i++)
if(n%i==0) return false;
return true;
}
分解質因數—試除法
題目連結:Acwing867. 分解質因數
暴力求質因數
下面i就是質因子,s是質因子i的階數。
暴力的時間複雜度O(n)
void divide(int n){
for(int i=2;i<=n;i++){
if(n%i==0){ //i一定是質數,因為下面n/=i,n一直在更新,如果是合數的話,已經被2除乾淨了
int s=0;//統計質因子的階數
while(n%i==0){
s++;
n/=i;
}
cout<<i<<" "<<s<<endl;
}
}
}
優化
給出一個重要性質:正整數n的因子中,最多包含1個大於 n \sqrt{n} n 的質因子。(可以用反證法來證明)
這裡試除法的優化就是列舉到 n \sqrt{n} n ,然後剩下的1個大於 n \sqrt{n} n 的質因子單獨處理,這樣試除法分解質因數的時間複雜度O( n \sqrt{n} n )
ac程式碼
#include<bits/stdc++.h>
using namespace std;
//試除法分解質因數
void divide(int n){
//處理≤sqrt(n)的質因子
for(int i=2;i<=n/i;i++){
if(n%i==0){
int s=0;
while(n%i==0){
s++;
n/=i;
}
cout<<i<<" "<<s<<endl;
}
}
//單獨處理大於sqrt(n)的質因子
if(n>1) cout<<n<<" "<<1<<endl;
}
int main(){
int n;
cin>>n;
int tmp;
for(int i=0;i<n;i++){
cin>>tmp;
divide(tmp);
cout<<endl;
}
}
篩質數
題目連結acwing868. 篩質數
樸素篩法
樸素篩法的思想:從小到大列舉所有數,每次刪掉該數的所有倍數,比如列舉到2,就刪掉所有2的倍數。列舉到3,就刪掉所有3的倍數。
需要用到prime陣列,用來存放所有的質數;st陣列,用來記錄是否是某個數的倍數,即判斷哪些是質數。需要count1變數,來儲存質數的個數。
遍歷的時候,需要考慮等號取不取。
樸素篩法的時間複雜度 O ( n × l o g n ) O(n\times logn) O(n×logn)
#include<bits/stdc++.h>
using namespace std;
const int maxn=1000010;
int prime[maxn];//prime陣列存的是從小到大的質數
int count1=0; //質數的個數
bool st[maxn]; //判斷是否是質數
//樸素的篩法
void primeNumber(int n){
for(int i=2;i<=n;i++){
if(!st[i]){ //i不是別人的倍數,那麼就是質數
prime[count1++]=i;
}
for(int j=i+i;j<=n;j+=i) st[j]=true;//倍數篩掉
}
cout<<count1<<endl;
}
int main(){
int n;
cin>>n;
primeNumber(n);
return 0;
}
埃氏篩法
優化:不需要篩掉所有數的倍數(合數的倍數一定不是質數,不用管),只需要篩掉質數的倍數,(因為質數的倍數是合數)。
//優化的篩法
void primeNumber1(int n){
for(int i=2;i<=n;i++){
if(!st[i]){ //i不是別人的倍數,那麼就是質數
prime[count1++]=i;
for(int j=i+i;j<=n;j+=i) st[j]=true;//質數的倍數篩掉
}
}
cout<<count1<<endl;
}
有質數定理:當n很大時,1~n中有 n l n n \frac{n}{lnn} lnnn個質數。
優化的篩法(埃氏篩法)時間複雜度 O ( n × l o g l o g n ) O(n\times loglogn) O(n×loglogn),可以粗略地看成是O(n)。
兩次提交結果
線性篩法
先說效率,數量級在10^7時候,線性篩法比埃氏篩法快一倍大概。
線性篩法時間複雜度O(n)
線性篩法的核心:一個數k只會被k的最小質因子篩掉。
//線性篩法
void primeNumber2(int n){
for(int i=2;i<=n;i++){
if(!st[i]) prime[count1++]=i;
for(int j=0;prime[j]<=n/i;j++){
st[prime[j]*i]=true;
if(i % prime[j]==0) break; //prime[j]一定是i的最小質因子
}
}
cout<<count1<<endl;
}