day04:数学(素数筛&质因数分解&最大公约数&最小公倍数&排列组合)
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 isprimes(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(isprimes(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<iostream>
#include<cstring>
using namespace std;
const int N=1e8+1;
bool isprimes[N]; //判断是否素数, 1是素数,0不是素数
int primes[N] pn=0; //primes[i]为第 i个素数, pn 为素数个数
//朴素方法(时间复杂度 O(sqrt(n))
bool isPrimes(int n){
for(int i=2; i*i<=n; i++){
if(n%i==0) return false;
}
return true;
}
//埃式筛法 - 埃拉托斯特尼筛法
//原理:筛除倍数,一个素数的倍数一定不是素数,时间复杂度 O(nlogn)
void Eratosthenes(int maxn){
//将isprime全部初始化为 1,相当于设置全部的 i都为质数
memset(isprimes, 1, sizeof(isprimes));
isprimes[0]=isprimes[1]=pn=0; //0,1不是质数
for(int i=2; i<=maxn; i++){
if(isprimes[i]==0) continue; //退出当前循环,继续下次循环
primes[++pn] = i; //将素数 i存入数组 primes
for(int j=2; i*j<=maxn; j++){
isprimes[i*j]=0; // i*j不是质数
}
}
}
//线性筛法 - 欧拉筛法,时间复杂度 O(n)
//原理:素数的倍数是合数,且合数具有唯一最小素因子
void FastSieve(int maxn){
// 初始化isprime全部为1,即初始化所有i为质数
memset(isprimes, 1, sizeof(isprimes));//0,1不是质数
isprimes[0] = isprimes[1] = pn = 0;
for(int i=2; i<=maxn; i++){
if(isprimes[i]) primes[++pn] = i;//将质数 i存入primes
for(int j=1; j<=pn; j++){
if(i*primes[j] > maxn) break;
isprimes[i*primes[j]] = 0;//质数的倍数都是合数
if(i%primes[j]==0) break; //利用最小素因子筛素数关键
}
}
}
int main() {
int n; cin>>n;
Eratosthenes(n);
// FastSieve(n);
//输出pn个素数
for(int i=1; i<=pn; i++) cout<<primes[i]<<" "; cout<<endl;
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 isPrimes(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(isPrimes(i) && isPrimes(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 isprimes[N];
int primes[N], pn=0;
bool isPrimes(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(isprimes, 1, sizeof(isprimes));
isprimes[0] = isprimes[1] = pn = 0;//0,1不是质数
for(int i=2; i<=maxn; i++){
if(isprimes[i]==0) continue;//退出当前循环,继续下次循环
primes[++pn] = i;//将质数 i存入primes
for(int j=2; i*j<=maxn; j++){
isprimes[i*j] = 0;//质数的倍数都是合数
}
}
}
//线性筛法 - 欧拉筛法
void FastSieve(int maxn){
// 初始化isprime全部为1,即初始化所有i为质数
memset(isprimes, 1, sizeof(isprimes));//0,1不是质数
isprimes[0] = isprimes[1] = pn = 0;
for(int i=2; i<=maxn; i++){
if(isprimes[i]) primes[++pn] = i;//将质数 i存入primes
for(int j=1; j<=pn; j++){
if(i*primes[j] > maxn) break;
isprimes[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 isprimes[N];
int primes[N], pn=0;
bool isPrimes(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(isprimes, 1, sizeof(isprimes));
isprimes[0] = isprimes[1] = pn = 0;//0,1不是质数
for(int i=2; i<=maxn; i++){
if(isprimes[i]==0) continue;//退出当前循环,继续下次循环
primes[++pn] = i;//将质数 i存入primes
for(int j=2; i*j<=maxn; j++){
isprimes[i*j] = 0;//质数的倍数都是合数
}
}
}
//线性筛法 - 欧拉筛法
void FastSieve(int maxn){
// 初始化isprime全部为1,即初始化所有i为质数
memset(isprimes, 1, sizeof(isprimes));//0,1不是质数
isprimes[0] = isprimes[1] = pn = 0;
for(int i=2; i<=maxn; i++){
if(isprimes[i]) primes[++pn] = i;//将质数 i存入primes
for(int j=1; j<=pn; j++){
if(i*primes[j] > maxn) break;
isprimes[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(isprimes[temp]==0) break;
temp /= 10;
}
if(temp==0) cout<<primes[i]<<endl;
}
}*/
return 0;
}
上面这个是用筛法程序做的,那么我们还是讲一讲取巧方法 - 打表
所谓打表,就是写一个最简单的程序,通过该程序得到部分中间数据,甚至是最终答案,从而达到能简化运算次数的目的。
#include<iostream>
#include<cmath>
using namespace std;
bool isPrimes(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(isPrimes(i)){
int temp=i;
while(temp){
if(isPrimes(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几分钟的事情