欧几里得算法(辗转相除法)
本次数论的基础
先给出代码
int gcd(int a, int b)
{
while (b != 0)
{
int c = a;
a = b;
b = c % b;
}
return a;
}
简单证明(不严谨,只是为了便于记忆):
a = kb + r
r = a % b
假设 c 为 a,b 的最大公约数
那么 c 可以整除 a,b,r 三者
因此 a,b 的最大公约数等于 b,r 的最大公约数
于是 gcd(a, b) = gcd(b, a%b)
当出现 gcd(a,0) 的情况时说明找到了最大公约数,为 a
扩欧(扩展欧几里得算法)
用于求解形如 ax + by = c (a,b,c均为整数)的方程的一组解
事实上,只有当 gcd(a,b) 能够整除 c 的时候方程才有解
因此通常情况下a,b是互质的,确保方程有解
先给出代码(默认c为1)
//在求解的同时返回a,b的最大公约数,建议将x y设置为全局变量
//最后的结果:x为a%b的逆元,y为b%a的逆元,结果可能会小于零,若小于零,手动x+=b,y+=a
int ex_gcd(int a, int b)
{
if (b == 0)//b==0时得到gcd的结果,即a'=gcd(a,b)这里的a'和a是两个值,所以x=1
{
x = 1;
y = 0;
return a;
}
int ans = ex_gcd(b, a % b, x, y);
//根据x',y'和x,y的关系逆推回去
int t = x;
x = y;
y = t - (a / b) * y;
return ans;//返回最小公约数
}
证明
费马小定理求逆元
若有 a p 互质,且 p 为质数,则 ap-1 =1 (mod p)
因此 ap-2 = x 就是 a 在模 p 下的逆元
这个定理的限制较多,只有在p为质数的条件下才能使用。
线性打逆元表
如果要求一段连续数字的逆元,有一种线性的方法(会用板子就行,证明略)
long long f[maxn];
void work(long long n, long long p)
{
f[1] = 1; // 1的逆元为1
for (long long i = 2; i <= n; i++)
{
f[i] = -(p / i) * f[p % i];
f[i] = (f[i] % p + p) % p;
}
}
欧拉函数
φ(n)表示小于等于n的正整数中与n互质的数的个数,计算方法如图(p是x的所有质因数)
int Phi(int x)
{
int ans = x;
for (int i = 2; i * i <= x; i++)
{
if (x % i == 0) // i是x的一个质因数
{
ans = ans / i * (i - 1);
while (x % i == 0)
x /= i;
}
}
if (x > 1) ans = ans / x * (x - 1);
return ans;
}
φ(n)具有以下性质:
如果 m,n 互质,则 φ(nm) = φ(m)φ(n)
利用这个性质再结合素数筛的思想可以线性求一段数字的欧拉函数:
long long phi[maxn], n, pr[maxn], tot = 0, ans = 0;
bool pd[maxn];
void getphi(int n)
{
phi[1] = 1;
for (int i = 2; i <= n; i++)
{
if (pd[i] = false)
{
pr[++tot] = i;
phi[i] = i - 1; //质数除了1以外都与自己互质
}
for (int j = 1; j <= tot && pr[j] * i <= n; j++)
{
long long t = i * pr[j];
pd[t] = true;
if (i % pr[j] == 0)
{
phi[t] = phi[i] * pr[j];
break;
}
else
phi[t] = phi[i] * (pr[j] - 1);
}
}
}
组合计数
卢卡斯定理 a,b很大时(1e18),并且要求对结果取模p,有以下结论
int quick(int a, int b, int p)
{
int res = 1;
while (b)
{
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
int C(int a, int b, int p)
{
if (b > a) return 0;
int res = 1;
for (int i = 1, j = a; i <= b; i++, j--)
{
res = res * j % p;
res = res * quick(i, p - 2, p) % p;
}
return res;
}
int lucas(int a, int b, int p)
{
if (a < p && b < p) return C(a, b, p);
return C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}