算法类——数学问题汇总
一、最大公约数和最小公倍数
最大公约数
正整数a与b的最大公约数是指a与b的所有公约数中最大的那个公约数。一般用gcd(a,b)来表示a和b的最大公约数。例如4和6的最大公约数是2,3和9的最大公约数是3。而求解最大公约数常用欧几里得算法(辗转相除法)。
定理:设a、b均为正整数,则gcd(a,b)=gcd(b,a%b)。
递归式代码:
int gcd(int a,int b)///递归调用
{
if(b==0)
{
return a;
}
gcd(b,a%b);
}
函数调用:
#include<algorithm>///直接使用c++的内置函数
using namespace std;
__gcd(int a,int b)
最小公倍数
最小公倍数的求解是在最大公约数的基础上进行的。一般用lcm(a,b)来表示a和b的最小公倍数。
这里需要用到一个公式:lcm(a,b)*gcd(a,b)=a*b
所以 lcm(a,b) = a*b/gcd(a,b)
但在a*b计算时有可能造成溢出,因此更恰当的写法是lcm(a,b) = a/gcd(a,b)*b
二、素数
素数又称质数,是指除了1和本身之外,不能被其他数整除的一类数。即对给定的正整数n,如果对任意的正整数a(1<a<n),都有n%a!=0成立,那么称n是素数,否则,如果存在a(1<a<n),有n%=0成立,那么称n为合数。应特别值得注意的是,1既不是素数,也不是合数。
素数的判断
注意到如果在2~n-1中存在n的约数,不妨设为k,即n%k==0,那么由k*(n/k)==n可知,n/k也是n的一个约数,且k与n/k中一定满足其中一个小于等于sqrt(n),另一个大于等于sqrt(n)。
bool isPrime(int n)
{
if(n<=1)
{
return false;
}
int sqr = (int)sqrt( (double)m );
for(i=2; i<=sqr; i++)
{
if(n%i==0)
return false;
}
return true;
}
如果n没有接近int型变量的范围上界,有更简单的写法:
bool isPrime(int n)
{
if(n<=1)
{
return false;
}
for(i=2; i*i<=n; i++)
{
if(n%i==0)
return false;
}
return true;
}
筛法求素数
逐一判断1~n范围内的素数,总的时间复杂度很高,这里引入筛法求素数。
筛法的理论依据,任何一个数都能拆成多个素数的乘积(唯一分解定理),其思想是筛掉范围内所有的合数,而合数由素数乘积的到,素数的倍数就是合数,那么出现一个素数,我们就筛掉它的倍数。
素数筛法代码:
const int maxn =101;//表长
int prime[maxn];//存放所有素数
int pNum=0;//素数的个数
bool p[maxn]= {0}; //如果i是素数,p[i]为false,否则,p[i]为true 初始化为0
void Find_Prime()
{
for(int i=2; i<maxn; i++)
{
if(p[i]==false) //如果i是素数
{
prime[pNum++]=i;//把素数i存放到prime数组中
for(int j=i*2; j<maxn; j+=i)
{
//筛去所有i的倍数,循环条件不能写成j<=maxn
p[j]=true;
}
}
}
}
求解100以内所有素数代码:
#include<stdio.h>
const int maxn =101;//表长
int prime[maxn];//存放所有素数
int pNum=0;//素数的个数
bool p[maxn]= {0}; //如果i是素数,p[i]为false,否则,p[i]为true 初始化为0
void Find_Prime()
{
for(int i=2; i<maxn; i++)
{
if(p[i]==false) //如果i是素数
{
prime[pNum++]=i;//把素数i存放到prime数组中
for(int j=i*2; j<maxn; j+=i)
{
//筛去所有i的倍数,循环条件不能写成j<=maxn
p[j]=true;
}
}
}
}
int main()
{
Find_Prime();
for(int i=0; i<pNum; i++)
{
printf("%d ",prime[i]);
}
return 0;
}
三、质因子分解
所谓质因子分解是指将一个正整数 n 写成一个或多个质数的乘积形式,例如 24=2*2*2*3。显然,由于最后都要归结到若干不同质数的乘积,不妨先把素数表打印出来。
由于每个质因子都可以不止出现一次,因此不妨定义结构体 factor ,用来存放质因子及其个数,如下所示:
struct factor
{
int x, cnt; // x 为质因子,cnt 为其个数
} fac[10];
而有一个结论:对一个正整数 n 来说,如果它存在 [2,n] 范围内的质因子,要么这些质因子全部小于等于 sqrt(n) ,要么只存在一个大于 sqrt(n) 的质因子,而其余质因子全部小于等于 sqrt(n) 。这就给进行质因子分解提供了一个很好的思路:
1.
枚举 1~sqrt(n) 范围内的所有质因子 p,判断 p 是否是 n 的因子。如果 p 是 n 的因子,那么给 fac 数组中增加质因子 p
,并初始化其个数为 0。然后只要 p 还是 n 的因子,就让 n 不断除以 p,每次操作令 p 的个数加 1,直到 p 不再是 n
的因子为止。如果p不是质因子,就直接跳过。
if(n % pri[i] == 0) // prime[i] 是质因子
{
fac[num].x = pri[i];//记录该质因子
fac[num].cnt = 0;
while(n%pri[i] == 0) // 计算该质因子的个数
{
fac[num].cnt++;
n /= pri[i];
}
num++; // 不同质因子个数 +1
}
2. 如果在上面步骤结束后 n 仍然大于 1,说明 n 有且仅有一个大于 sqrt(n) 的质因子(有可能是n本身),这时需要把这个质因子加入 fac 数组,并令其个数为 1。
if(n != 1) // 存在大于 sqrt(n) 的质因子
{
fac[num].x = n;
fac[num++].cnt = 1;
}
至此,fac 数组中存放的就是质因子分解的结果,时间复杂度为O(根号n)。