day04:數學(素數篩&質因數分解&最大公約數&最小公倍數&排列組合)
質因數分解
【題目描述】對於正整數N的質因數分解,指的是將其寫成:N=p1*p2*…*pm
其中,p1,p2,…,pm為不下降的質數。給定N,輸出其質因數分解的形式。
輸入格式:1個正整數N。
輸出格式:N的質因數分解的形式N=p1*p2*…*pm
,其中p1,p2,…,pm都是質數而且p1<=p2<=…<=pm。
輸入樣例:60
輸出樣例:60=2*2*3*5
bool isprime(int m) {
for(int i=2; i*i<=m; i++)
if(m%i==0)return 0;
return 1;
}
for(int i=2; i<=n; i++){
if(isprime(i)&&n%i==0) {
while(n%i==0) {
tot++;
if(tot==1) printf("%d=%d",n,i)
else printf("*%d",i);
n/=i;
}
}
}
這個算法跑的非常慢,無法滿足N較大時的需求。
我們發現對於所有數字我們都判斷過i是否是一個質數,而事實上我們並不需要。
因為加入n%i==0,那么i必然是一個質數!證明如下:
假設i不是質數,i必存在一個質數因子j,那么j<i,但之前已經枚舉過了j,
n已經把質數j全部分解出去了,當前的n已經沒有了j因子n%j!=0,與n%i==0矛盾!
所以當n%i==0時,i必然為一個質數。
int main(){
int n,total=0; cin>>n;
for(int i=2; i*i<=n; i++){
while(n%i==0){
total++;
if(total==1) cout<<n<<"="<<i;
else cout<<"*"<<i;
n /= i;
}
}
return 0;
}
素數篩
在上述求素數的方法中我們所使用的方法是對每一個數進行素數判斷,如果這個數沒有除1和它本身外的其它因數,那么該數就是因數。
但是這樣的算法時間復雜度是線性的,能不能優化一下呢?肯定是可以的,首先我們發現如下規律:
2是素數,那么2*2=4不是素數,2*3=6不是素數,2*4=8不是素數...
3是素數,那么3*2=6不是素數,3*3=9不是素數,3*4=12不是素數...
於是想到既然不是素數了,那么就沒必要判斷了呀,可以直接篩掉 - 埃式篩法
對於埃式篩法,發現會對同一個數多次篩除,這沒必要啊,於是就有了 - 線性篩法
#include<bits/stdc++.h>
using namespace std;
const int N=1e8+1;
bool isprime[N];//判斷是否素數, 1表示素數,0表示不是素數
int primes[N];//素數記錄
int pn=0;//素數個數記錄
//朴素方法(時間復雜度 O(sqrt(n))
bool isPrime(int n){
for(int i=2; i*i<=n; i++){
if(n%i==0) return false;
}
return true;
}
//埃式篩法 - 埃拉托斯特尼篩法
//篩除倍數,一個素數的倍數一定不是素數,時間復雜度 O(loglogn)
void Eratosthenes(int maxn){
//將isprime全部初始化為 1,相當於設置全部的 i都為質數
memset(isprime, 1, sizeof(isprime));
isprime[0]=isprime[1]=pn=0;//0,1不是質數
for(int i=2; i<=maxn; i++){
if(isprime[i]==0) continue;//退出當前循環,繼續下次循環
primes[++pn]=i;//將素數 i存入數組 primes
for(int j=2; i*j<=maxn; j++){
isprime[i*j]=0;// i*j不是質數
}
}
}
//線性篩法 - 歐拉篩法,時間復雜度 O(n)
void FastSieve(int maxn){
// 初始化isprime全部為1,即初始化所有i為質數
memset(isprime, 1, sizeof(isprime));//0,1不是質數
isprime[0] = isprime[1] = pn = 0;
for(int i=2; i<=maxn; i++){
if(isprime[i]) primes[++pn] = i;//將質數 i存入primes
for(int j=1; j<=pn; j++){
if(i*primes[j] > maxn) break;
isprime[i*primes[j]] = 0;//質數的倍數都是合數
if(i%primes[j]==0) break;//利用最小因數篩素數關鍵
}
}
}
int main() {
int n; cin>>n;
// Eratosthenes(n);
FastSieve(n);
for(int i=0; i<n; i++){
if(isprime[i]==1) cout<<i<<" ";
}cout<<endl;
for(int i=1; i<=pn; i++){
cout<<primes[i]<<" ";
}return 0;
}
最大公約數
約數,其實就是因數:比如 9=1*9=3*3
,那么1,3,9就是9的約數
公約數是指,兩個數的相同約數,比如:9=1*9=3*3, 6=1*6=2*3
, 那么1,3就是他們的公約數
最大公約數就是公約數中的最大值
求a,b最大公約數的方法有三種
- 最小遞減方法: 取a,b的最小值,判斷是否滿足公約數條件,滿足即為最大公約數,否則-1繼續判斷,直到滿足,最小取1.
- 更相減損法:
gcd(a,b) = gcd(a-b, b);//a>=b
- 輾轉相除法:
gcd(a,b) = gcd(b, a%b);
#include<bits/stdc++.h>
using namespace std;
//最小遞減法
int gcd1(int a, int b){
for(int i=min(a,b); i>=1; i--){
if(a%i==0 && b%i==0) return i;
}
}
//更相減損法
int gcd2(int a, int b){
if(a<b) swap(a,b);
if(a-b==0) return b;
return gcd2(a-b, b);
}
//輾轉相除法 - 歐幾里德法
int gcd3(int a, int b){
if(b==0) return a;
return gcd3(b, a%b);
}
int main(){
int a=12,b=12; cin>>a>>b;
cout<<gcd1(a, b)<<endl;
cout<<gcd2(a, b)<<endl;
cout<<gcd3(a, b)<<endl;
return 0;
}
最小公倍數
倍數,就是指一個數能被另一個數整除,則就稱這個數是另一個數的倍數,如:6%6=0, 6%3=0, 6%2=0, 6%1=0, 則6是6,3,2,1的倍數
公倍數:是指一個數同時是兩個數的倍數,如:6是2,3的倍數,12是2,3的倍數,則6,12為2,3的公倍數。
最小公倍數:是指倍數中最小的那個數,如:2,3的最小公倍數為6
- 最大遞增法:取a,b最大值,判斷是否滿足公倍數條件,滿足則該數就是最小公倍數,否則+1繼續判斷
int lcm(int a, int b){
for(int i=a>b? a:b; ; i++){
if(i%a==0 && i%b==0) return i;
}
}
- 定理法:兩個數的最大公約數與最小公倍數的乘積等於這兩個數的乘積
int lcm(int a, int b){
return a*b/gcd(a,b);
}
排列組合
排列:是指按座位順序排列,有先后順序之分,比如:現要5名同學坐在3個椅子上,每名同學各不相同,各個椅子不同,問有多少中落座方案?
A(5,3) = 5!/(5-3)! = 5*4*3=60
組合:是指5名同學中任意3名同學選擇落座,每名同學各不相同,但各個椅子相同,也就是無落座先后順序之分。
C(5,3) = A(5,3)/A(3,3) = 5!/2!/3! = 10
公式:
A(n, m) = n!/(n-m)!
A(n, 1) = n
C(n, m) = A(n, m)/A(m, m) = n!/(n-m)!/m!
C(n, m) = C(n, n-m)
C(n, 0) = C(n, n) = 1
可以把從n個數中選m個數的組合分為2類
1. 不含第n個數的組合,方案數為:C(n-1, m)
2. 含第n個數的組合,方案數為:C(n-1, m-1)
所以C(n, m) = C(n-1, m)+C(n-1, m-1)
1. P1075 [NOIP2012 普及組] 質因數分解
【題目描述】已知正整數n是兩個不同的質數的乘積,試求出兩者中較大的那個質數。
輸入格式:一個正整數n。
輸出格式:一個正整數pp,即較大的那個質數。
輸入樣例:21
輸出樣例:7
數據范圍:n≤2×10^9
題解:
#include<iostream>
using namespace std;
bool isPrime(int n){//判斷n是不是素數
if(n<2) return false;
for(int i=2; i*i<=n; i++)
if(n%i==0) return false;
return true;
}
int main_60(){
int n;cin>>n;
for(int i=n-1; i>=2; i--){
if(isPrime(i) && isPrime(n/i) && n%i==0){
cout<<i;break;//return 0;
}
}return 0;
}
int main_100() {
int n; cin>>n;
// 唯一分解定理:一個數能且只能分解為一組質數的乘積
for(int i=2; i<=n; i++) {
if(n%i==0) { //最小的質因數
cout<<n/i; //輸出最大的質因數
break;//return 0;
}
}return 0;
}
2. P1029 [NOIP2001 普及組] 最大公約數和最小公倍數問題
【題目描述】輸入兩個正整數 x0,y0,求出滿足下列條件的 P, Q 的個數:
- P,Q 是正整數。
- 要求 P, Q以 x0 為最大公約數,以 y0 為最小公倍數。
試求:滿足條件的所有可能的 P, Q 的個數。
輸入格式:一行兩個正整數x0,y0
輸出格式:一行一個數,表示求出滿足條件的 P,Q 的個數
輸入樣例:3 60
輸出樣例:4
題解:
#include<bits/stdc++.h>
using namespace std;
//最大公約數
int gcd(int a, int b){
if(b==0) return a;
else return gcd(b, a%b);
}
//最小公倍數
int lcm(int a, int b){
//定理:兩個數的乘積等於它們最大公約數和它們最小公倍數的積。
return a*b/gcd(a,b);
}
int main(){
int x, y, count=0; cin>>x>>y;
for(int p=1; p<=max(x,y); p++){//對p進行枚舉
//定理:兩個數的乘積等於它們最大公約數和它們最小公倍數的積。
//因為 x,y是 p,q的最大公約數和最小公倍數
//所以 p*q = x*y
//由於 x,y已知,p是枚舉的,所以可以計算 q=x*y/p;
int q=x*y/p; //根據枚舉的p的值,計算對應的q值
//按照最大公約數最小公倍數判斷結果是否正確
if(x==gcd(p, q) && y==p*q/gcd(p,q)){
count++;
}
}
cout<<count;return 0;
}
3. P3383 【模板】線性篩素數
【題目描述】給定一個范圍 n,有 q 個詢問,每次輸出第 k 小的素數。
提示:如果你使用 cin 來讀入,建議使用 std::ios::sync_with_stdio(0)
來加速。
輸入格式:
第一行包含兩個正整數 n,q,分別表示查詢的范圍和查詢的個數。
接下來 q 行每行一個正整數 k,表示查詢第 k 小的素數。
輸出格式:輸出 q 行,每行一個正整數表示答案。
輸入樣例:
100 5
1
2
3
4
5
輸出樣例:
2
3
5
7
11
數據范圍:對於 100% 的數據,n = 10^8, 1≤q≤10^6,保證查詢的素數不大於 n。
題解:
#include<bits/stdc++.h>
using namespace std;
const int N=1e8+1;
int a[N];
bool isprime[N];
int primes[N], pn=0;
bool isPrime(int num){
if(num<2) return 0;
for(int i=2; i*i<=num; i++){
if(num%i==0) return 0;
return 1;
}
//埃式篩法 - 埃拉托斯特尼篩法
//篩除倍數,一個素數的倍數一定不是素數
void Eratosthenes(int maxn){
// 初始化isprime全部為1,即初始化所有i為質數
memset(isprime, 1, sizeof(isprime));
isprime[0] = isprime[1] = pn = 0;//0,1不是質數
for(int i=2; i<=maxn; i++){
if(isprime[i]==0) continue;//退出當前循環,繼續下次循環
primes[++pn] = i;//將質數 i存入primes
for(int j=2; i*j<=maxn; j++){
isprime[i*j] = 0;//質數的倍數都是合數
}
}
}
//線性篩法 - 歐拉篩法
void FastSieve(int maxn){
// 初始化isprime全部為1,即初始化所有i為質數
memset(isprime, 1, sizeof(isprime));//0,1不是質數
isprime[0] = isprime[1] = pn = 0;
for(int i=2; i<=maxn; i++){
if(isprime[i]) primes[++pn] = i;//將質數 i存入primes
for(int j=1; j<=pn; j++){
if(i*primes[j] > maxn) break;
isprime[i*primes[j]] = 0;//質數的倍數都是合數
if(i%primes[j]==0) break;//利用最小因數篩素數關鍵
}
}
}
int main(){
int n=100, q=10;
scanf("%d%d", &n, &q);
for(int i=1; i<=q; i++){
scanf("%d", &a[i]);
}
// Eratosthenes(n);//埃氏篩法
FastSieve(n);//線性篩法
for(int i=1; i<=q; i++){
printf("%d\n",primes[a[i]]);
}return 0;
}
4. P1218 [USACO1.5]特殊的質數肋骨 Superprime Rib
【題目描述】農民約翰的母牛總是產生最好的肋骨。你能通過農民約翰和美國農業部標記在每根肋骨上的數字認出它們。農民約翰確定他賣給買方的是真正的質數肋骨,是因為從右邊開始切下肋骨,每次還剩下的肋骨上的數字都組成一個質數。
舉例來說:7 3 3 1 全部肋骨上的數字 7331 是質數;三根肋骨 733 是質數;二根肋骨 73 是質數;當然,最后一根肋骨 7 也是質數。7331被叫做長度 4 的特殊質數。
寫一個程序對給定的肋骨的數目 n,求出所有的特殊質數。1 不是質數。
輸入格式:一行一個正整數 n。
輸出格式:按順序輸出長度為 n 的特殊質數,每行一個。
輸入樣例:4
輸出樣例:
2333
2339
2393
2399
2939
3119
3137
3733
3739
3793
3797
5939
7193
7331
7333
7393
數據范圍:對於 100% 的數據,1≤n≤8。
題解:
#include<bits/stdc++.h>
using namespace std;
const int N=1e8+1;
int a[N];
bool isprime[N];
int primes[N], pn=0;
bool isPrime(int num){
if(num<2) return 0;
for(int i=2; i*i<=num; i++)
if(num%i==0) return 0;
return 1;
}
//埃式篩法 - 埃拉托斯特尼篩法
//篩除倍數,一個素數的倍數一定不是素數
void Eratosthenes(int maxn){
// 初始化isprime全部為1,即初始化所有i為質數
memset(isprime, 1, sizeof(isprime));
isprime[0] = isprime[1] = pn = 0;//0,1不是質數
for(int i=2; i<=maxn; i++){
if(isprime[i]==0) continue;//退出當前循環,繼續下次循環
primes[++pn] = i;//將質數 i存入primes
for(int j=2; i*j<=maxn; j++){
isprime[i*j] = 0;//質數的倍數都是合數
}
}
}
//線性篩法 - 歐拉篩法
void FastSieve(int maxn){
// 初始化isprime全部為1,即初始化所有i為質數
memset(isprime, 1, sizeof(isprime));//0,1不是質數
isprime[0] = isprime[1] = pn = 0;
for(int i=2; i<=maxn; i++){
if(isprime[i]) primes[++pn] = i;//將質數 i存入primes
for(int j=1; j<=pn; j++){
if(i*primes[j] > maxn) break;
isprime[i*primes[j]] = 0;//質數的倍數都是合數
if(i%primes[j]==0) break;//利用最小因數篩素數關鍵
}
}
}
int main(){
// freopen("test.out", "w", stdout);
int n=4; cin>>n;
if(n == 8) {//這一點確實有點難,只能使用打表或者二分思想來解決
cout<<"23399339"<<endl
<<"29399999"<<endl
<<"37337999"<<endl
<<"59393339"<<endl
<<"73939133"<<endl;return 0;
}
n = pow(10,n);
// Eratosthenes(n);//埃氏篩法
FastSieve(n);//線性篩法
for(int i=n/10; i<=n-1; i++){
int temp=i;
while(temp){
if(isprime[temp]==0) break;
temp /= 10;
}
if(temp==0) cout<<i<<endl;
}
/* for(int i=1; i<=n; i++){
if(primes[i] > n-1) break;
if(n/10 <= primes[i]){
int temp = primes[i];
while(temp){
if(isprime[temp]==0) break;
temp /= 10;
}
if(temp==0) cout<<primes[i]<<endl;
}
}*/
return 0;
}
上面這個是用篩法程序做的,那么我們還是講一講取巧方法 - 打表
所謂打表,就是寫一個最簡單的程序,通過該程序得到部分中間數據,甚至是最終答案,從而達到能簡化運算次數的目的。
#include<iostream>
#include<cmath>
using namespace std;
bool isPrime(int n){
if(n<2) return 0;
for(int i=2; i*i<=n; i++)
if(n%i==0) return 0;
return 1;
}
void pd() {//按要求打表 - 用朴素算法
int n; cin>>n;
n=pow(10,n);
for(int i=2; i<=n; i++){
if(isPrime(i)){
int temp=i;
while(temp){
if(isPrime(temp)==0) break;
temp /= 10;
}
if(temp==0) cout<<i<<",";
}
}
}
const int N=10;
int primes[N][20]={//按要求對素數(1~8位)打表
{},
{2,3,5,7},
{23,29,31,37,53,59,71,73,79},
{233,239,293,311,313,317,373,379,593,599,719,733,739,797},
{2333,2339,2393,2399,2939,3119,3137,3733,3739,3793,3797,5939,7193,7331,7333,7393},
{23333,23339,23399,23993,29399,31193,31379,37337,37339,37397,59393,59399,71933,73331,73939},
{233993,239933,293999,373379,373393,593933,593993,719333,739391,739393,739397,739399},
{2339933,2399333,2939999,3733799,5939333,7393913,7393931,7393933},
{23399339,29399999,37337999,59393339,73939133}};
int main() {
int n; cin>>n;
for(int i=0; primes[n][i]!=0; i++){
cout<<primes[n][i]<<endl;
}return 0;
}//相對線性篩法而言,這道題我更推薦使用打表的方法來解決,不過也就10幾分鍾的事情