数学,特别是数论,在计算机科学中有着重要地位。
辗转相除法求最大公约数:
这里有一个题,是一个数学的思路。线段上格点的个数,注释:格点是指横纵坐标均为整数的点。
如果使用暴力遍历的方法,检查满足min(x1, x2) ≤ x ≤ max(x1, x2),min(y1, y2) ≤ y ≤ max(y1, y2)的格点。这种做法当然可以找出正确答案,但其复杂度为O(|x1-x2| × |y1-y2|),对于数据较大的输入很难处理,我们便使用数学方法优化此题解法。
这道题数学方法:|x1-x2| 和 |y1-y2| 的最大公约数-1(还需要特判一下x1-x2和y1-y2为0)。
接下来就讨论最大公约数的求法了,一个基础的算法叫做辗转相除法,代码:
int gcd(int a, int b)
{
if(b == 0) return a;
return gcd(b, a % b);
}
也可以使用三目运算符,写出来更简化:
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
这个可以很容易记下来。
还记得c++库中自带了求最大公约数的函数__gcd(x, y),两个下划线加gcd其中写上两参数。
这个算法的时间复杂度当然很小了,O(log max(a, b))。
继续介绍扩展欧几里得算法,这个曾经做过题,题解连接:
洛谷 P1082 [NOIP2012 提高组] 同余方程 欧几里得算法 扩展欧几里得算法 贝祖定理 乘法逆元 最大公约数 整除与剩余
其中也有 同余方程 欧几里得算法 扩展欧几里得算法 贝祖定理 乘法逆元 最大公约数 整除与剩余等记录。
对辗转相除法做一些扩展,求“整数x和y使得ax+by=1”,很显然,如果gcd(a, b) ≠ 1, 无解。如果gcd(a, b) = 1, 求ax+by=gcd(a, b)。
int exgcd(int a, int b, int& x, int& y)
{
int d = a;
if(b != 0)
{
d = exgcd(b, a % b, y, x);
y -= (a / b) * x;
}
else
{
x = 1;
y = 0;
}
}
下面继续介绍有关素数的基础算法。
素性测试,这可能是大家做过的最基础的数学算法之一,给定一个整数n,判断n是不是素数。
最简单的算法,利用定义进行枚举:
bool is_prime(int n)
{
for(int i=2;i*i<=n;i++)
{
if(n%i==0) return false;
}
return n != 1;
}
这里还有不同形式的两个算法,约数枚举和整数分解。
约数枚举:
vector<int> divisor(int n)
{
vector<int> res;
for(int i=1;i*i<=n;i++)
{
if(n % i == 0)
{
res.push_back(i);
if(i!=n%i) res.push_back(n/i);
}
}
return res;
}
整数分解:
map<int, int> prime_factor(int n)
{
map<int, int> res;
for(int i=2;i*i<=n;i++)
{
while(n%i==0)
{
++res[i];
n /= i;
}
}
if(n!=1) res[n]=1;
return res;
}
此算法的时间复杂度在O(sqrt(n)),已经很高效了,当然,如果要使用效率更加高效的算法,我们不妨看一下埃氏筛法。
问题:给定整数n,问n以内有多少素数。
这个算法的思路是将2到n范围内所有整数写下来,从最小的2,将2的倍数都划去,接着3,把三的倍数划去,以此类推,最后剩下的就是素数。
int prime[MAX_N];
bool is_prime[MAX_N + 1];
int sieve(int n)
{
int p = 0;
for(int i=0;i<=n;i++) is_prime[i] = true;
is_prime[0] = is_prime[1] = false;
for(int i=2;i<=n;i++)
{
if(is_prime[i])
{
prime[p++] = i;
for(int j=2*i; j<=n;j+=i) is_prime[j] = false;
}
}
return p;
}
关于素数最后还有一个区间筛,求a,b两点之间的素数个数,当然,可以使用埃氏筛把从2到a的素数个数求出,再把从2到b的素数个数求出,就可做出。
下面介绍一下模运算,经常有题目的结果超出64位整数范围,要求取余mod一个数,或者使用高精度算法,这个其他文章有解释:
这是高精乘的一篇文章:
洛谷 P5703 苹果采购 简单乘法 P1303 A*B Problem 高精度乘法 高精度算法 进位 数据范围 基本运算
关于求模运算,一些数学思想:
a+b≡c+d(mod m)
a-b≡c-d(mod m)
a×b≡c×d(mod m)
最后,再介绍一个重要的算法,快速幂算法。
求幂运算怎么求?pow(a, b),可以,还有一个很重要且巧妙的方法——快速幂。
typedef long long ll;
ll mod_pow(ll x, ll n, ll mod)
{
ll res = 1;
while(n > 0)
{
if(n & 1) res = res * x % mod;
x = x * x % mod;
n >>= 1;
}
return res;
}
其时间复杂度为O(log n),还可以使用递归实现:
ll mod_pow(ll x, ll n, ll mod)
{
if(n == 0) return 1;
ll res = mod_pow(x * x % mod, n / 2, mod);
if(n & 1) res = res * x % mod;
return res;
}