1、欧几里得算法(辗转相除法):
关键在于恒等式gcd(a, b) = gcd(b, a mod b),边界条件为gcd(a, 0) = a,程序如下:
int gcd(int a, int b){
return b == 0 ? a : gcd(b, a % b);
}
虽然是递归,但是保证不会栈溢出。可以证明,gcd函数的递归层数不超过4.785 * lgN + 1.6723,其中N = max{a, b}。
而a和b的最小公倍数lcm(a, b)为a / gcd(a, b) * b,不可以写为a * b / gcd(a, b),因为a * b可能会溢出。
2、素数定理:π(x) ~ x / ln(x),其中π(x) 表示不超过x的素数个数,~ 表示近似的意思。
素数筛选法:
memset(vis, 0, sizeof(vis));
for(int i = 2; i <= n; i++)
for(int j = i * 2; j <= n; j += i)
vis[j] = 1;
我们可以对这份代码进行改进:
memset(vis, 0, sizeof(vis));
int m = sqrt(n + 0.5);
for(int i = 2; i <= m; i++)
if(!vis[i])
for(int j = i * i; j <= n; j++)
vis[j] = 1;
int cnt = 0;
for(int i = 2; i <= n; i++)
prime[cnt++] = i;
3、同余模公式:
(a + b) mod n = ((a mod n) + (b mod n)) mod n
a * b mod n = (a mod n) * (b mod n) mod n
(a - b) mod n = ((a mod n) - (b mod n) + n) mod n
a * b mod n代码如下:(当n = 10 ^ 9时,a * b mod n一定在int范围内,但a mod n和b mod n的乘积可能会超过int,需要用long long保存中间结果)
int mul_mod(int a, int b, int n){
a %= n;
b %= n;
return (int)((long long)a * b % n);
}
4、由组合数公式C(n, k) = n! / (k! * (n - k)!),得到等式C(n, k) = ((n - k + 1) / k) * C(n, k - 1)
5、数论中的计数问题:
(1)给出正整数n的唯一分解式n = p1 ^ a1 * p2 ^ a2 ... pk ^ ak,求n的正约数个数。
对于n的某个素因子pi,它在所求约数中的指数可以是0,1, 2,...,ai共ai + 1种情况,所以n的正约数个数为:(a1 + 1) * (a2 + 1) ... (ak + 1)
(2)给出正整数n的唯一分解式n = p1 ^ a1 * p2 ^ a2 ... pk ^ ak,求1,2,3...,n中与n互素的数的个数。
由容斥原理得到欧拉函数:φ(n) = n * (1 - 1 / p1) * ( 1 - 1 / p2) ... (1 - 1 / pk)
代码如下:
int euler_phi(int n){
int m = sqrt(n + 0.5);
int ans = n;
for(int i = 2; i <= m; i++)
if(n % i == 0){
ans = ans / i * (i - 1);
while(n % i == 0) n /= i;
}
if(n > 1) ans = ans / n * (n - 1);
}
如果需要求出1~n中所有数的欧拉函数值,可以用于筛选法求素数类似的方法:
void phi_table(int n, int* phi){//比较难理解,注意好好理解!!
for(int i = 2; i <= n; i++) phi[i] = 0;
phi[1] = 1;
for(int i = 2; i <= n; i++)
if(!phi[i])
for(int j = i; j <= n; j += i) {
if(!phi[j]) phi[j] = j;
phi[j] = phi[j] / i * (i - 1);
}
}
6、费马小定理及欧拉定理
费马小定理:若p为质数,且(a, p) = 1,则a ^ (p - 1) % p == 1;即若p为质数,且a, p互质,那么 a的(p-1)次方除以p的余数恒等于1。
欧拉定理:a ^ (φ(n)) % n == 1,其中φ(n)为欧拉函数,φ(n) = n * (1 - 1 / p1) * ( 1 - 1 / p2) ... (1 - 1 / pk),p1, p2, ..., pk为n的的唯一分解式的素因子。
7、编码与解码:
任给一个字符串,求它按照字典序的编号<编码>;或者反过来给一个字符串的字典序编号,求该字符串<解码>。
结论:设字符一共有k类,个数分别为n1, n2, ..., nk,则这个多重集的全排列个数为(n1 + n2 + ... + nk)! / (n1! * n2! ... nk!)
例如有两个a、一个b和一个c组成的串,求串caba的编号;以及求编号为8的字符串。
(1)设输入串为S,记d(S)为S的各个排列中,字典序比S小的串的个数,记f(x)表示多重集x的全排列个数。
则d(S) = d(caba) = f(cba) + f(caa) + d(aba) = f(cba) + f(caa) + d(ba) = f(cba) + f(caa) + f(b) + d(a) = f(cba) + f(caa) + f(b)
由f(cba) = (1 + 1 + 1)! / (1! * 1! * 1!) = 6,f(caa) = (1 + 2)! / (1! * 2!) = 3,f(b) = 1得
d(S) = 10
所以串caba的编号为11;
(2)因为比所求字符串小的字符串第一个字符可取a、b和c,若分别取定后,由f(cba) = 6,f(caa) = 3,f(aab) = 3且所给编号为8得知:
所求字符串第一个字符为b(因为6 + 3 >= 8);比所求字符串小的字符串第二个字符可取a和c,若分别取定后,由f(ca) = 2,f(aa) = 1得知:
所求字符串第二个字符为a(因为6 + 2 >= 8);比所求字符串小的字符串第三个字符可取a和c,若分别取定后,由f(c) = 1,f(a) = 1得知:
所求字符串第三个字符为c(因为6 + 1 + 1 = 8),从而第四个字符为a,故所求字符串为baca!