- 一些数论基本定理
① 合成律
( a + b ) mod c ≡ ((a mod c) + (b mod c)) mod c
( a * b ) mod c ≡ ((a mod c) * (b mod c)) mod c
② 消去律
若 gcd (c,p ) = 1,则ac ≡ bc mod p => a b mod p
下面给出证明:
ac ≡ bc mod p => ac = bc + kp => c(a - b) = kp,c 和 p 互质=>
(1) c 能整除 k 或 (2) a = b
如果(2)不成立,则 c|kp
c 和 p 没有公因子 => c|k ,所以 k = ck’ => c(ab)= kp 可以表示为 c(ab) = ck’p
因此,ab = k’p ,得出 a ≡ b mod p - 快速幂取模
代码:
int PowMod (int a, int b, int c)
{
//快速求 a^b % c ,要避免计算中间结果溢出
int result = 1;
int base = a % c
while (b) {
if (b & 1)
result = (result * base) % c;
base = (base * base) % c;
b >>= 1;
}
return result;
}
- Sn = a+a^2 +…+a^n,要求Sn mod p。
(1) 若 n 是偶数
(2) 若 n 是奇数
代码实现:
struct Matrix
{
int m[maxn][maxn];
}result, base;
Maxtrix Mul(Matrix A, Matrix B, int n)
{
Matrix tmp;//定义一个临时的矩阵,存放A*B的结果
memset(tmp.m, 0, sizeof(tmp.m));
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
for (int k = 1; k <= n; k++)
{
tmp.m[i][j]+=A.m[i][k] * B.m[k][j];
}
}
}
return temp;
}
- 矩阵快速幂 + 等比数列二分求和取模
首先我们来了解一下矩阵的乘法用代码 该如何表示:
设矩阵A,B为 n 阶方阵,那我们都知道
很显然,这里使用一个三循环就可以实现的,下面上代码:
void QuickPow(int n, int x)
{
/*整数的快速幂ans初始化为1,对于矩阵快速幂,ans应该初始化为单位矩阵*/
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (i == j)
{
result.m[i][j] = 1;
}
else
{
result.m[i][j] = 0;
}
}
}
while (x)
{
if (x & 1)
{
result = Mul(result, base);
}
res = Mul(base, base);
x >>= 1;
}
}
下面实现矩阵快速幂:
void QuickPow(int n, int x)
{
/*整数的快速幂ans初始化为1,对于矩阵快速幂,ans应该初始化为单位矩阵*/
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
if (i == j)
{
ans.m[i][j] = 1;
}
else
{
ans.m[i][j] = 0;
}
}
}
while (x)
{
if (x & 1)
{
ans = Mul(ans, res);
}
res = Mul(res, res);
x >>= 1;
}
}
- 欧几里得算法求最大公约数
int gcd(int x, int y)
{
return y == 0 ? x : gcd(y, x % y);
}
由此也可以求出最小公倍数:lcm(x, y) = x * y * gcd(x, y)
6. 扩展欧几里得算法
ax+by = gcd (a,b) 有整数解 x,y
<=>bx + (a%b)y = gcd (a,b ) 有整数解 (x1, y1),且
ax+by = gcd (a,b) 中的x = y1, y = x1 - [a / y1 ] [ ] 是去尾取整
因此,可以在求gcd(a,b) 的同时,对 ax + by = gcd(a,b) 求解。
实现代码:
int GcdEx(int x, int y, int a, int b)
{
if (b == 0)
{
x = 1;
y = 0;//不知道为啥
return a;
}
int x1, y1;
int gcd = GcdEx(b, a % b, x1, y1);
x = y1;
y = x1 - a / b * y1;
return gcd;
}
ax + by = c 有整数解的充要条件是gcd(a, b) | c
设 d = gcd(a,b), k = c / d,ax + by = d 的解是 x1,y1,则 ax + by = c 解集是:
x = k * x1 + t * (b / d)
y = k * y1 - t * (a / d)
- 扩展欧几里得算法求 ax ≡ c(mod b)
① 求 ax ≡ c(mod b) 等价于求 ax + by = c
设 d = gcd(a, b), k = c / d,ax + by = d 的解是x1, y1。
故 ax≡c (mod b) 的解集是:
x = k * x1 + t * (b / d),t 为任意整数
其中模 b 不同的解共有 d 个,为:
x = k * x1 + t * (b / d) ,t = 0,1,…d-1
② 求 ax c(mod b) 的最小非负 整数解:
令 ans = k,s = b / d;
则 x = ans + t * s,t 为任意整数
则最小非负整数解是:
(ans % s + s) % s
③ 例题:对下面的循环:
for (variable = A; variable != B; variable += C)
statement;
A,B,C和 variable 都是 K(K<=32) 比特的无符号数 运算的结果也是 K 比特的无符号数,给定 A,B,C 和 K ,问循环执行多少次。
分析:实际上就是求 A + CX B (mod 2 K ) 的最小非负整数解
CX ≡(B A) (mod 2 K ) - 线性筛法求素数
朴素筛法求 n 以内的所有质数:
- 初始时容器内为 2 到 n 的所有数
- 取出最小的数 p,p 一定是质数 删去除 p 外的所有 p 的倍数
- 重复上述步骤直到向后找不到没被删掉的数
缺陷:一个数可能被重复删去多次,影响效率
改进:对每个素数 p 考虑所有 i , 若 p 小于等于 i 的最小素因子则将 i * p 去掉
实现代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
int n;
cin >> n; //求 n 以内素数
vector<int> prime;
vector<bool> isPrime(n + 1, true);
for (int i = 2; i <= n; i++)
{
if (isPrime[i])
{
prime.push_back(i);//处理到 i 时它还没被删掉,则 i 为素数
}
for (int j = 0; j < prime.size(); j++)
{
//prime[j]是 i 的最小素因子
if (i * prime[j] <= n)
{
isPrime[i * prime[j]] = false;
}
else
{
break;
}
if (i % prime[j] == 0)
{
break;
}
}
}
for (int i = 0; i < prime.size(); i++)
{
cout << prime[i] << endl;
}
return 0;
}
- 欧拉函数、欧拉定理和费马小定理
欧拉函数
① 欧拉函数φ(n) = 小于 n 且和 n 互质的正整数 (包括 1) 的个数(n 为正整数);
② 完全余数集合:Zn = { 小于 n 且和 n 互质的数 } |Zn| = φ( n);
③ 对于素数 p,φ( p) = p -1。
欧拉定理
互质的正整数 a 和 n ,有 a ^ φ( n)≡ 1 mod n
费马小定理
若正整数
a 与素数 p 互质,则有 a ^ (p - 1) ≡ 1 mod p
欧拉函数的公式
(1) 若 p 为素数,正整数 n = p ^ k ,则φ(n) = p^k - p^(k - 1)
(2) p,q 是两个互质的正整数, n = pq 则 φ(n) = φ( p)φ(q)
(3) 当 b 是质数,a%b = 0 ,则φ(ab) = φ(a)b
(4) 对任意 n: φ(n) = n(1 - 1/p1 )(1 - 1/p2 )…(1 - 1/pn),n = p1^a1 * p2^a2 … pn^an (pi 为素数)
(5) 递推式质数 p 满足 p | x, 若 p^2 | x,则 φ( p) = φ (x/p) * p若 p^2 | x 不成立,则 φ( p) = φ(x / p)*(p - 1)
下面代码实现递推式求欧拉函数:
int euler(int n)
{
int ret = 1;
for (int i = 2; i * i < n; i++)
{
if (n % i == 0)
{
n /= i, ret *= i - 1;
while (n % i == 0)
{
n /= i, ret *= i;
}
}
}
if (n > 1)
{
ret *= n - 1;
}
return ret;
}