一、整除
a, b ∈ Z,且 b ≠ 0,若存在 c ∈ Z,使得 b = ac,则称 b 被 a 整除,a 整除 b,记作 a | b。又称 b 是 a 的倍数,a 是 b 的约数。
性质
设 a, b, c ∈ Z,
- 若 a | b 且 b | a,则 a = ±b
- 若 a | b 且 b | c,则 a | c
- 若 a | bc,且 (a, b) = 1,则 a | c
- 若 a | b,且 a | c,则 a | (b, c),a | bx + cy(x, y ∈ Z)
- a | c 且 b | c,则 [a, b] | c;当 (a, b) = 1 时,ab | c
[注释]:数论中,(a, b) 表示 gcd(a, b);[a, b] 表示 lcm(a, b)。(小学奥数里好像也有)
以上性质的正确性证明略。
二、带余除法
对于任意的 a, b ∈ Z(设 a ≥ b 且 b ≠ 0),存在唯一的商 q 和余数 r,使得 a = bq + r(其中 0 ≤ r ≤ |b|)。
注意:该定理包括负数。
(小学应该就会了)
三、模运算
在 C++ 中,% 表示取模,a % b 返回 a 除 b 的余数。
性质
- 加法:(a + b) % p = (a % p + b % p) % p
- 减法:(a - b) % p = (a % p - b % p) % p
- 乘法:(a * b) % p = ((a % p) * (b % p)) % p
- 幂:(ab) % p = ((a % p)b) % p
- 模运算满足结合律、交换律和分配律
以上性质的正确性证明均可采用设 a = k1p + r1,b = k2p + r2,0 ≤ r1, r2 ≤ |p| 的方法证明。
同余
有两个整数 a, b,若它们除以正整数m所得的余数相等,则称 a, b 对于模 m 同余,记作:
a ≡ b (mod m)
若 a, b 同余,则 m | a - b。
同余的性质
- 反身性:a ≡ a (mod m)
- 对称性:若 a ≡ b (mod m),则b ≡ a (mod m)
- 传递性:若 a ≡ b (mod m),b ≡ c (mod m),则a ≡ c (mod m)
- 同余式相加减:若 a ≡ b (mod m),c ≡ d (mod m),则a ± c ≡ b ± d (mod m)
- 同余式相乘:若 a ≡ b (mod m),c ≡ d (mod m),则 a * c ≡ b * d (mod m)
以上性质的正确性证明略。
四、前缀和与差分
前缀和
前缀和用于以 O(1) 的时间复杂度求一个区间内所有数的和。
// 初始化
for (int i = 1; i <= n; ++i)
sum[i] = a[i] + sum[i - 1];
// 求解 [i, j] 中所有数的和
ans = sum[j] - sum[i - 1];
进阶:
【二维前缀和】
根据容斥原理,可知:
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
sum[i][j] = a[i][j] + sum[i - 1][j] + sum[i][j - 1]
- sum[i - 1][j - 1];
// 求解 a[x][y] 到 a [z][w]:
ans = sum[z][w] - a[x - 1][y] - a[x][y - 1] + a[x - 1][y - 1];
不明白的看一下:
S = S4 + S2 + S3 - S1。
差分
类似前缀和,用于维护一个被不断修改的区间。
简单来说,有多次操作使 [a, b] 内的每个数都 ±w。
// 初始化
for (int i = 1; i <= n; ++i)
diff[i] = a[i] - a[i - 1];
// [i, j] 中所有数加 w
diff[i] += w; diff[j + 1] -= w;
// [i, j] 中所有数减 w
diff[i] -= w; diff[j + 1] += w;
// 将 diff 数组还原为 a 数组
for (int i = 1; i <= n; ++i)
a[i] = diff[i] + a[i - 1];
可以这么想:diff 数组中第 i 个数存的是 a[i] 与a[i - 1] 的差值,若 a[i] ~ a[j] 每个数加上 w,则直接 a[i] += w
即可,因为 diff 数组记录的都是后一个数减前一个数的值,a[i] 相当于比之前的 a[i] 大 w,则 a[i + 1] 比 a[i] 大 diff[i + 1] + w,但还是比新的 a[i + 1] 大 diff[i + 1]。
五、快速幂与快速乘
先讲快速幂:
1.快速幂原理
快速幂算法的核心思想就是每一步都把指数分成两半,而相应的底数做平方运算。这样不仅能把非常大的指数给不断变小,所需要执行的循环次数也变小,而最后表示的结果却一直不会变。
以 211 为例,其等于2 ^ ( 20 + 21 + 23)。
若从 11 逆推,则:
用 r 表示结果; 用 base 表示底数。
- r = 1,base = a
- 11 % 2 = 1,11 / 2 = 5
- r *= base = base 1,base = base * base = base2
- 5 % 2 = 1,5 / 2 = 2
- r *= base = base 3,base = base * base = base4
- 2 % 2 = 0,2 / 2 = 1
r *= base = base 7,base = base * base = base8- 1 % 2 = 1,1 / 2 = 0
- r *= base = base 11,base = base * base = base16(第二步不在运算范围内)
- 此时幂数为 0,应乘以 1(省略),直接返回
Δ 时间复杂度为O(log₂N)
2.快速幂实现
需加语句、头文件:
typedef long long ll;
快速幂常规算法:
ll qPow(ll a, ll b)
{
ll r = 1, base = a;
while (b)
{
if (b & 1) // 若 b 为奇数,则 b & 1 == 1;若 b 为偶数,则 b & 1 == 0
r = r * base;
base = base * base;
b >>= 1; // 相当于 b /= 2;同理,b <<= 1 相当于 b *= 2
}
return r;
}
快速幂取模算法:
ll qmPow(ll a, ll b, ll m)
{
ll r = 1, base = a % m;
while (b)
{
if (b & 1)
r = (r * base) % m;
base = (base * base) % m;
b >>= 1;
}
return r;
}
2.快速乘实现
快速乘与快速幂思想一样,代码类似,只不过将乘换成了加
ll qmMul(ll a, ll b, ll m)
{
ll r = 0, base = a % m;
while (b)
{
if (b & 1)
r = (r + base) % m;
base = (base + base) % m;
b >>= 1;
}
return r;
}
作者:Rotch
日期:2020-09-24
修改:2020-05-12(五稿)
[2020-09-24]:修正了一些错误
[2020-10-01]:用角标替换 ^
[2021-05-03]:修正了一些错误,加入了快速乘部分
[2020-05-12]:更改为数论基础