简单数论问题,归根结底有这么几个:
- 质数问题(判断、筛法、分解、分解的几个常用结论)和约数问题
- 快速幂、矩阵快速幂
- gcd,exgcd(同余方程组问题) 欧拉函数
- 逆元
- 高等数学相关:高斯消元、组合数问题
其余更加复杂的数学问题在读高级篇的时候再说
讲解部分参考了《算法竞赛进阶指南》和acwing的课程。
质数的判定
试除法,只要除到根号n就可以了,因为两数相乘等于n,一定一个大于等于根号n,一个小于等于根号n。
bool is_prime(int x)
{
if(x<2)
return false;
for(int i=2;i<=sqrt(x);i++)
{
if(x%i==0)
return false;
}
return true;
}
埃氏筛法
结果我都是推进一个vector再做后续处理。
bool primes(int x)
{
for(int i=2;i<=x;i++)
{
if(v[i])
continue;
prime.push_back(i);
for(int j=i;j<=x/i;j++)
v[i*j]=1;
}
}
如果是从[a,b)筛素数,质因数一定不超过sqrt(b)。在[2,sqrt(b))筛素数,把筛选的素数的倍数从[a,b)划掉即可。
质因数分解
任意一个大于1的数N都可以分解为:
leetcode周赛做过一道题,问不同质因子相乘能得到多少个数。就是c1+c2+…+cn
分解的结果我习惯放进一个pair数组中,再进行后续处理。
void divide(int x)
{
for(int i=2;i<=sqrt(x);i++)
{
if(x%i==0)
{
int cnt=0;
while(x%i==0)
{
x/=i;
cnt++;
}
v.push_back({i,cnt});
}
}
if(x>1)
{
v.push_back({x,1});
}
}
}
注意不要漏掉x是素数的情况。
进阶指南中提到的几个质数和约数的公式:
N!中质因子p的个数为:
N中的每个约数都能表示为
约数的个数:
所有正约数的和为:
快速幂
ll qmi(ll a,ll b,ll mod)
{
ll ans=1;
while(b)
{
if(b&1)
{
ans=a*ans%mod;
}
a=a*a%mod;
b>>=1;
}
return ans;
}
矩阵快速幂
以一道最简单的数据较大的斐波那契说明:
f(n)=f(n-1)+f(n-2)
组合f(n)和f(n-1),写成矩阵形式为:
就可以通过这个简单矩阵的n次方递推了。
初始化的时候是一个单位矩阵,并且乘的时候用一个函数实现矩阵乘法(矩阵用一个记录维度和内容的结构体存储):
struct matrix {
int a[100][100];
int n;
};
//矩阵乘法的实现
matrix matrix_mul(matrix A, matrix B, int mod) {
matrix ret;
ret.n = A.n;
//初始化结果矩阵
for (int i = 0; i < ret.n; i++) {
for (int j = 0; j < ret.n; j++){
ret.a[i][j] = 0;
}
}
//按照矩阵乘法的定义,i,j是正在计算的数在矩阵中的位置。由于矩阵乘法是很多个数相加,所以用k去枚举i行乘j列乘到了第几个数、
for (int i = 0; i < ret.n; i++) {
for (int j = 0; j < ret.n; j++) {
for (int k = 0; k < A.n; k++) {
ret.a[i][j] = (ret.a[i][j] + A.a[i][k] * B.a[k][j] % mod) % mod;
}
}
}
return ret;
}
//初始化单位矩阵。相当于快速幂里ans=1,矩阵快速幂中ans=unit
matrix unit(int n)
{
matrix ret;
ret.n=n;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(i==j)
{
ret.a[i][j]=1;
}
else
{
ret.a[i][j]=0;
}
}
}
return ret;
}
matrix matrix_pow(matrix A,int p,int mod)
{
matrix res=unit(A.n);
for(;p;p/=2)
{
if(p&1)
{
res=matrix_mul(res,A,mod);
}
A=matrix_mul(A,A,mod);
}
return res;
}
之后的题目,如果用矩阵快速幂的话公式会比较难推,要多加练习。
gcd
int gcd(int a,int b)
{
if(b)
return gcd(b,a%b);
return a;
}
exgcd
不要纠结怎么来的,推了式子写法还要适应递归。
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
int r=exgcd(b,a%b,x,y);
int temp=y;
y=x-(a/b)*y;
x=temp;
return r;
}
exgcd的运用场景:
求ax+by=c的解,给出a,b,c解出x, y
若c mod gcd(p,q)=0 则解为:之前算法算出的解 乘上c/gcd(p,q),否则没有整数解。
如果要求的是最小的整数解或者全部解
可以利用扩展欧几里得算法得出ax+by=gcd(a,b)的一个解(x1,y1):
然后求得ax+by=c的解为;
x=x1*c/gcd(a,b)
y=y1*c/gcd(a,b);
书上的题目
线段上格点的个数 gcd
像这种格点很容易和gcd或者欧拉函数结合起来
题目的形象化表示如图:
所以答案是gcd(x,y)-1。减去1是因为i=gcd(x,y)的时候,就是线段的一个端点,不应该算进去。
双六 exgcd
得看出一个式子
ax+by=1
首先说明:一定存在整数对(x,y)使得ax+by=gcd(a,b)
当1不是gcd(a,b)的时候是无解的。
当1是gcd(a,b)的时候用exgcd来求解(x,y)