今天主要学习了数论,算法模板很多,思想很重要。
1.素数
1)素数定理:不超过 n 的素数的个数 π(n) → n / ln(n) (这样开数组可以开小点,防止TLE)
2)筛选法素数
埃氏筛:用所有小于等于√n 的数来筛去合数。
bool isprime[MAXN + 1];
void getPrime() {
memset(isprime, true, sizeof(isprime));
isprime[0] = isprime[1] = false;
for(int i = 2; i <= MAXN / i; i++) { //比sqrt()快
if(isprime[i]) {
for(int j = i * i; j <= MAXN; j += i) {
isprime[j] = false;
}
}
}
}
欧拉筛的:在维护 isprime[] 的同时维护 prime[],用于存储已经找好的素数。遍历 2 ~ n,对于每一个数 i,首先,若 i 是素数,则加入到 prime[],接着遍历 prime[],将 i * prime[j] 筛去,如果 prime[j] 能够整除 i 则停止。
bool isprime[MAXN+1];
int prime[MAXN/10], tot; //防止MLE
void getPrime() {
memset(isprime, true, sizeof(isprime));
isprime[0] = isprime[1] = false;
tot = 0;
for(int i = 2; i < MAXN; i++) {
if(isprime[i]) prime[tot++] = i;
for(int j = 0; j < tot && prime[j] <= MAXN / i; j++) {
isprime[i * prime[j]]=false;
if(i % prime[j] == 0) break;
}
}
}
2.算术基本定理(唯一分解定理)
算术基本定理:每个大于 1 的正整数都可以被唯一地写成素数的乘积,即 n = p1γ1p2γ2 ... ptγt,其中 p1 < p2 < ... < pt 且都为素数。(证明不作详述)
性质: n 的约数个数= (γ1 + 1)(γ2 + 1)...(γt + 1)(约数个数定理)
n 的约数之和
= (p10+p11+...+p1γ1)(p20+p21+...+p2γ2)...(pt0+pt1+...+ptγt) (γi,αi,βi是次方,不会操作,弄的看着不像)
设 a = p1α1p2α2...ptαt, b = p1β1p2β2...ptβt,则
gcd(a, b) = p1min(α1, β1)p2min(α2, β2) ... ptmin(αt, βt),
lcm(a, b) = p1max(α1, β1)p2max(α2, β2) ... ptmax(αt, βt) ,
来一道题
LightOJ-1236 对于正整数 n,求有多少数对 (a, b) 满足 lcm(a, b) = n 且 1 ≤ a ≤ b ≤ n。n ≤1014,T ≤ 200
分析:
首先不对a,b做限制,忽略a ≤ b
设 n=p1γ1p2γ2 ... ptγt,a=p1α1p2α2...ptαt,b= p1β1p2β2...ptβt
n=a*b=gcd(a,b)*lcm(a,b)
等号两边对比由 γi=max(αi,βi) 可知由 2*γi+1 求和中取法,设为s
考虑a ≤ b
对于符合题意的(i,j),那么(j,i)也一定符合题意,特殊的情况是(n,n)
综上考虑可知结果为1+s/2
3.素因子分解
1)时间复杂度O(sqrt(n))
LL factor[100][2]; //factor[i][0]存底数,factor[i][1]存指数
int cnt;
void getFactors(LL n) {
cnt = 0;
for(int i = 2; i <= n / i; i++) {
if(n % i == 0) {
factor[cnt][0] = i;
factor[cnt][1] = 0;
while(n % i == 0) {
factor[cnt][1]++;
n /= i;
}
cnt++;
}
}
if(n != 1) {
factor[cnt][0] = n;
factor[cnt][1] = 1;
cnt++;
}
}
2)时间复杂度O(sqrt(n)/logn),但要提前维护好素数表
LL factor[100][2]; //factor[i][0]存底数,factor[i][1]存指数
int cnt;
void getFactors(LL n) {
cnt = 0;
for(int i = 0; i < tot && prime[i] <= n / prime[i]; i++) {
if(n % prime[i] == 0) {
factor[cnt][0] = prime[i];
factor[cnt][1] = 0;
while(n % prime[i] == 0) {
factor[cnt][1]++;
n /= prime[i];
}
cnt++;
}
}
if(n != 1) {
factor[cnt][0] = n;
factor[cnt][1] = 1;
cnt++;
}
}
4.欧几里得算法(辗转相除法)
引理:如果 a = bq + r,那么 gcd(a, b) = gcd(b, r)
证明:“ e | b 且 e | r ” ←→ “ e | (bq + r) 且 e | b ”,所以 b, r 的公因子与 bq + r, b 的公因子完全相同,所以 gcd(bq + r, b) = gcd(b, r),即 gcd(a, b) = gcd(b, r)
性质:lcm(a, b) = ab / gcd(a, b)
LL gcd(LL a, LL b) {
return b == 0 ? a : gcd(b, a % b);
}
LL lcm(LL a, LL b) {
return a / gcd(a, b) * b; //先除后乘
}
5.拓展欧几里得算法
引理 1:gcd(a, b) 等于 a 和 b 的线性组合中最小的正整数
证明:首先,设 d 是 a 和 b 的线性组合中最小的正整数,d = ma + nb,设 r = a mod d = a - q(ma + nb) = (1 - qm)a - qnb,则 r 是 a 和 b 的线性组合且 0 ≤ r < d,得 r = 0,因此 d | a,同理 d | b。其次,如果 c | a 且 c | b,那么由 d = ma + nb 可得 c | d,因此 d = gcd(a, b)。
引理 2:所有 a 和 b 的线性组合都是 gcd(a, b) 的倍数,所有 gcd(a, b) 的倍数都是 a 和 b 的线性组合。
证明:① 设 d = gcd(a, b),则 d | a 且 d | b,所以对于任意整数 r, s,都有 d | (ra + sb)。 ② 设 d = gcd(a, b),由引理 1,d = ma + nb,所以对于任意整数 j,都有 jd = (jm)a + (jn)b。
结论:丢番图方程 ax + by = C 有解当且仅当 gcd(a, b) | C(c被gcd(a,b)整除)
设 d = gcd(a, b),先解方程 ax + by = d:
由等式 bx + (a % b) y
= bx + (a - [a / b] · b) y
= ay + b(x - [a / b] · y)
若方程 bx + (a % b) y = d 有解 (x1, y1),
则方程 ay + b(x - [a / b] · y) = d 也有解 (x1, y1),
则方程 ax + by = d 有解 (y1, x1 - [a / b] · y1)。
算法:对于方程 ax + by = d,先解方程 bx + (a % b) y = d,再将 (x1, y1) 转化为 (y1, x1 - [a / b] · y1),递归求解即可。
LL exgcd(LL a, LL b, LL &x, LL &y) {
if(b == 0) {
x = 1;
y = 0;
return a;
}
else {
LL d = exgcd(b, a % b, x, y);
y = x - a / b * y;
x = y;
return d;
}
}
另一种写法(比较常用):
LL exgcd(LL a, LL b, LL &x, LL &y) {
if(b == 0) {
x = 1;
y = 0;
return a;
}
else {
LL d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
}
利用扩展欧几里德算法求丢番图方程 ax + by = C 符合特定条件的解的步骤:
①LL d = exgcd(a, b, x, y),
如果 C % d != 0 则无解,
如果 C % d == 0,
则此时的 (x, y) 为方程 ax + by = d 的某一组特解。
②将上述解乘以 (C / d),得到对应原方程的解,但该解的数值范围是不定的。
③通解公式 x = x0 + (b / d)t,y = y0 - (a / d)t,其中 t ∈ Z
while(?) {x -= b / d; y += a / d; }
while(?) {x += b / d; y -= a / d; }
6.逆元
定理就不写了,逆元就是求(a/b)%p,求出b的逆元,就可以转化为 (a*inv(b))%p
1)扩展欧几里德算法求乘法逆元
设 a 关于模 p 乘法的逆元为 x,得 ax % p = 1,这个式子可以写成 ax = kp + 1,亦即 ax + pk = 1, k ∈ Z。
LL inv(LL a, LL p) {
LL x, k;
LL d = exgcd(a, p, x, k);
if(d == 1) return (x % p + p) % p;
else return -1;
}
2)递推法求乘法逆元
考虑关于模 p 乘法,a 的逆元和 (p % a) 的逆元的关系
结论:inv(a) = inv(p % a) ×p (p - [p / a])
证明:易知 p % a = p - [p / a] × a 两边同取模 p,得 p % a = (p - [p / a] × a) % p
同乘 inv(p % a),得 1 = inv(p % a) × (p - [p / a] × a) % p
同乘 inv(a),得 inv(a) = inv(p % a) × (p - [p / a]) % p
时间复杂度:O(log p) 要求:a < p 且 a 与 p 互质
LL inv(LL a, LL p){
if(a == 1) return 1;
return inv(p % a, p) * (p - p / a) % p;
}
递推法求 [1, n] 内所有数的模 p 乘法逆元
时间复杂度:O(n) 打表,O(1) 查询
LL inv[MAXN];
void getinv() {
inv[1] = 1;
for(int i = 2;i < MAXN; i++) {
inv[i] = (p - p / i) * inv[p % i] % p;
}
}
3)费马小定理求乘法逆元
费马小定理:设 p 是一个素数,a 是一个与 p 互素的正整数,则 ap-1 ≡ 1 (mod p)。
将 ap-1 % p = 1 两边同乘 inv(a) ,得 inv(a) = ap-2 % p
一般 a < 109,p = 1000000007,符合定理条件,但传统的求幂方法显然速度很慢.
LL inv(LL a, LL p) {
return pow_mod(a, p-2, p);
}
7.快速幂
快速幂在前面也有写过,这里直接提供上面代码中%p意义下的快速幂
LL pow_mod(LL a, LL n, LL p){
LL ret = 1;
LL tmp = a;
while(n) {
if(n & 1) ret = (ret * tmp) % p;
tmp = tmp * tmp % p;
n >>= 1;
}
return ret;
}
矩阵快速幂
typedef long long LL;
const int SIZE=20;
const LL MOD=1000000007;
struct Matrix {
int r, c;
LL mat[SIZE][SIZE];
Matrix() {}
Matrix(int _r, int _c) {
r = _r;
c = _c;
memset(mat, 0, sizeof(mat));
}
};
Matrix operator + (Matrix a, Matrix b) {
Matrix s(a.r, a.c);
for(int i = 0; i < a.r; i++) {
for(int j = 0; j < a.c; j++) {
s.mat[i][j] = (a.mat[i][j] + b.mat[i][j]) % MOD;
}
}
return s;
}
Matrix operator * (Matrix a, Matrix b) {
Matrix s(a.r, b.c);
for(int i = 0; i < a.r; i++) {
for(int k = 0; k < a.c; k++) { //j, k交换顺序运行更快
for(int j = 0; j < b.c; j++) {
s.mat[i][j] = (s.mat[i][j] + a.mat[i][k] * b.mat[k][j]) % MOD;
}
}
}
return s;
}
Matrix pow_mod(Matrix a, LL n) {
Matrix ret(a.r, a.c);
for(int i = 0; i < a.r; i++) {
ret.mat[i][i]=1; //单位矩阵
}
Matrix tmp(a);
while(n) {
if(n&1) ret = ret * tmp;
tmp = tmp * tmp;
n >>= 1;
}
return ret;
}