数论模板总结
作者:harryhe
日期:2018.11.4
一.对运算的优化:
1.快速乘
int fastmul(int a, int b, int p) {
int x = 0 ;
while (b) {
if (b & 1) x = (x + a) % p ;
a = (a + a) % p ;
b >>= 1 ;
}
return x ;
}
2.快速幂
int power(int a, int b, int p){
int x = 1 ;
for (; b; b >>= 1, a = a * a % p) if (b & 1) x = x * a % p ;
return x ;
}
O ( l o g b ) O(logb) O(logb)
就是按 2 2 2进制分解,按位如果当前位为 1 1 1就乘上相应的权值,权值可以递推的求,而不用于处理
二.最大公约数(gcd)
1.辗转相除法
int gcd(int a, int b) {
if (b == 0) return a ;
else return gcd(b, a % b) ;
}
2.二进制算法
int gcd(int x, int y) {
int i, j ;
if (!x) return y ;
if (!y) return x ;
for (i = 0;(x & 1) == 0; i++) x >>= 1 ;
for (j = 0; (y & 1) == 0; j++) y >>= 1 ;
if (j < i) i = j ;
while (1) {
if (x < y) swap(x, y) ;
if ((x -= y) == 0) return y << i ;
while ((x & 1) == 0) x >>= 1 ;
}
}
如果
x
=
y
x = y
x=y,
gcd
(
x
,
y
)
=
x
\gcd(x,y)=x
gcd(x,y)=x,否则
1)
x
,
y
x,y
x,y均为偶数,
gcd
(
x
,
y
)
=
gcd
(
x
/
2
,
y
/
2
)
∗
2
;
\gcd(x,y)=\gcd(x/2,y/2)*2;
gcd(x,y)=gcd(x/2,y/2)∗2;
2)
x
x
x为偶数,
y
y
y为奇数,
gcd
(
x
,
y
)
=
gcd
(
x
/
2
,
y
)
;
\gcd(x,y)=\gcd(x/2,y);
gcd(x,y)=gcd(x/2,y);
3)
x
x
x为奇数,
y
y
y为偶数,
gcd
(
x
,
y
)
=
gcd
(
x
,
y
/
2
)
;
\gcd(x,y)=\gcd(x,y/2);
gcd(x,y)=gcd(x,y/2);
4)
x
,
y
x,y
x,y均为奇数,
gcd
(
x
,
y
)
=
gcd
(
x
−
y
,
y
)
;
\gcd(x,y)=\gcd(x-y,y);
gcd(x,y)=gcd(x−y,y);
三.对素数的研究:
1.判断一个数是不是质数
bool isprime(int x) {
if (x == 0 || x == 1) return false ;
for (int i = 2; i <= sqrt(x); i++)
if (x % i == 0) return false ;
return true ;
}
被然以一个小于等于 ( x ) \sqrt(x) (x)的非 1 1 1正整数整除的数都不是质数
2.质因数分解
void factor(int x) {
tot = 0 ;
for (int i = 2; i <= sqrt(x); i++)
if (x % i == 0){
while (x % i == 0) fac[++tot] = i, x /= i ;
}
if (x > 1) fac[++tot] = x ;
}
对于小于等于
(
x
)
\sqrt(x)
(x)的数枚举是不是
x
x
x的因数,如果是,应当记录其个数
最后特判一个大于
(
x
)
\sqrt(x)
(x)的大因数,比如
106
=
2
∗
53
106=2*53
106=2∗53
3.素数筛—朴素算法
void Select(int n) {
for (int i = 2; i <= n; i++) f[i] = 1 ;
f[0] = f[1] = 0 ;
tot = 0 ;
for (int i = 2; i <= n; i++) {
if (!f[i]) continue ;
prime[++tot] = i ;//记录素数
for (int j = 2; i * j <= n; j++) f[i * j] = 1 ;
}
}
O
(
n
l
o
g
l
o
g
n
)
O(nloglogn)
O(nloglogn)
若
i
i
i为素数,
i
∗
j
i*j
i∗j肯定为合数
4.素数筛—线性筛
void select(int n) {
for (int i = 2; i <= n; i++) f[i] = 1 ;
f[0] = f[1] = 0 ;
tot = 0 ;
for (int i = 2; i <= n; i++) {
if (!f[i]) continue ;
prime[++tot] = i ;
for (int j = 1; j <= tot; j++) {
if (i * prime[j] > n) break ;
f[i * prime[j]] = 0 ;
if (i % prime[j] == 0) break ; //保证只被最小的质因数筛到
}
}
}
O
(
n
)
O(n)
O(n)
在暴力筛法基础上进行优化,即每一个合数只被它的最小素因子筛掉
四.同余即其应用
1.扩展欧几里得(exgcd)
int exgcd(int a, int b, int &x, int &y) {
if (b == 0) {
x = 1, y = 0 ;
return a ;
}
int d = exgcd(b, a % b, y, x) ;
y -= (a / b) * x ;
return d ;
}
求解形如
a
x
+
b
y
=
c
ax+by=c
ax+by=c的方程
如果
c
%
gcd
(
a
,
b
)
!
=
0
c\%\gcd(a,b)!=0
c%gcd(a,b)!=0无解
求解出
a
x
′
+
b
y
′
=
gcd
(
a
,
b
)
ax'+by'=\gcd(a,b)
ax′+by′=gcd(a,b)的一对特解
(
x
′
,
y
′
)
(x',y')
(x′,y′)
x
=
x
′
∗
a
/
gcd
(
a
,
b
)
,
y
=
y
′
∗
b
/
gcd
(
a
,
b
)
x=x'*a/\gcd(a,b),y=y'*b/\gcd(a,b)
x=x′∗a/gcd(a,b),y=y′∗b/gcd(a,b)
通解
X
=
x
+
k
∗
b
/
gcd
,
Y
=
y
−
k
∗
a
/
gcd
,
X=x+k*b/\gcd,Y=y-k*a/\gcd,
X=x+k∗b/gcd,Y=y−k∗a/gcd,
gcd
=
gcd
(
a
,
b
)
\gcd=\gcd(a,b)
gcd=gcd(a,b)
2.求解线性方程
运用 e x g c d exgcd exgcd可以求出形如 a x + b y = c ax+by=c ax+by=c的二元不定方程的一个特解
bool Equation(int a, int b, int c, int &x, int &y) { // 求解形如ax+by=c的方程
int d = exgcd(a, b, x, y) ;
if (c % d) return false ;//当c不是gcd(a,b)的公倍数时无解
int k = c / d ;
x *= k, y *= k ; // 同时扩大c/gcd(a,b)
return true ;
}
同样的求解出一组满足方程 a ′ x + b ′ y = gcd ( a , b ) a'x+b'y=\gcd(a,b) a′x+b′y=gcd(a,b),再扩大 c / gcd ( a , b ) c/\gcd(a,b) c/gcd(a,b)倍
3.乘法逆元及其求法
模不满足同除性,即 a ≡ b ( m o d p ) , a≡b(mod \ p), a≡b(mod p),不一定满足 a / c ≡ b / c ( m o d p ) a/c≡b/c(mod \ p) a/c≡b/c(mod p)
这种时候可以运用乘法逆元
若 a ∗ x ≡ 1 ( m o d b ) a*x≡1(mod\ b) a∗x≡1(mod b), a , b a,b a,b互质,则称 x x x为 a a a的乘法逆元,即为 a − 1 a^{-1} a−1
乘法逆元有如下几种求解方式:
1)乘法逆元 – 线性方法
void initinv(int n, int p) {
inv[1] = 1 ;
for (int i = 2; i <= n; i++) inv[i] = (ll) (p - p / i) * inv[p % i] % p ;
}
首先 1 − 1 = 1 ( m o d p ) 1^{-1}=1(mod \ p) 1−1=1(mod p)
然后设 p = k ∗ i + r , r < i , 1 < i < p p=k*i+r,r<i,1<i<p p=k∗i+r,r<i,1<i<p
把上式放入 m o d p mod \ p mod p意义下就会发现
k ∗ i + r ≡ 0 ( m o d p ) k*i+r≡0(mod \ p) k∗i+r≡0(mod p)
同时乘以 i − 1 i^{-1} i−1, r − 1 r^{-1} r−1就会得到
k ∗ r − 1 + i − 1 ≡ 0 ( m o d p ) k*r^{-1}+i^{-1}≡0(mod \ p) k∗r−1+i−1≡0(mod p)
i − 1 = − k ∗ r − 1 ( m o d p ) i^{-1}=-k*r^{-1}(mod \ p) i−1=−k∗r−1(mod p)
i − 1 = − [ p i ] ∗ ( p m o d i ) − 1 ( m o d p ) i^{-1}=-\left[\dfrac{p}{i}\right]*(p \ mod \ i)^{-1}(mod \ p) i−1=−[ip]∗(p mod i)−1(mod p)
于是上述我们可以推出逆元了
i n v [ i ] = − ( p / i ) ∗ i n v [ p % i ] inv[i] = -(p/i) * inv[p \% i] inv[i]=−(p/i)∗inv[p%i]
− − > i n v [ i ] = ( p − p / i ) ∗ i n v [ p % i ] --> inv[i] = (p - p / i) * inv[p \% i] −−>inv[i]=(p−p/i)∗inv[p%i]
2)乘法逆元 – exgcd
int getinv(int a, int p) { // 可以求出单个inv
ll x, y ;
ll d = exgcd(a, p, x, y) ;
return d == 1 ? (x + p) % p ? -1 ;
}
$a*x≡1(mod\ b) -> ax+by=1 $
3)乘法逆元 — 快速幂
int getinv(int a, int p) { // inv = a ^ (p - 2)
return power(a, p - 2, p) ; // 调用快速幂
}
根据费马小定理,当p为质数时, a − 1 = a p − 2 a^{-1}=a^{p-2} a−1=ap−2
4.中国剩余定理
ll CRT(int n){
for (int i = 1; i <= n; i++) scanf("%lld%lld", &m[i], &a[i]) ;
ll sum = 1, ans = 0 ;
for (int i = 1; i <= n; i++) sum *= m[i] ;
for (int i = 1; i <= n; i++) M[i] = sum / m[i] ;
for (int i = 1; i <= n; i++){
exgcd(M[i], m[i], x, y) ;
t[i] = (x % m[i] + m[i]) % m[i] ;
}
for (int i = 1; i <= n; i++) ans = (ans + a[i] * M[i] * t[i]) % sum ;
if (ans < 0) ans += sum ;
return ans ;
}
中国剩余定理是求解这种同余方程的:
{ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) . . . x ≡ a n ( m o d m n ) \begin{cases}x≡a_1(mod \ m_1)\\x≡a_2(mod \ m_2)\\...\\x≡a_n(mod \ m_n)\end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧x≡a1(mod m1)x≡a2(mod m2)...x≡an(mod mn)
若保证 m 1 , m 2 , . . . , m n m_1,m_2,...,m_n m1,m2,...,mn互质, S = ∏ i = 1 n m i S=\prod\limits_{i=1}^nm_i S=i=1∏nmi, M i = S m i M_i=\frac{S}{m_i} Mi=miS, t i t_i ti是线性同余方程 M i t i ≡ 1 ( m o d m i ) M_it_i≡1(mod \ m_i) Miti≡1(mod mi)的一组解
在保证有节的情况下
a n s = ∑ i = 1 n a i M i t i ans = \sum\limits_{i=1}^n{a_iM_it_i} ans=i=1∑naiMiti
且仅有一组解
5.高次同余方程算法Baby-step-Gaint-step(BSGS)
ll BSGS(ll a, ll b, ll p){
map<long ,long> hash ;
hash.clear();
b %= p ;
int t = (int)sqrt(p) + 1 ;
for(int j = 0; j < t; j++) {
int val = (ll) b * power(a, j, p) % p ;
hash[val] = j ;
}
a = power(a, t, p) ;
if (a == 0) {
if (b == 0) return 1 ;
else return -1 ;
}
for(int i = 0; i <= t; i++){
int val = power(a, i, p) ;
int j = hash.find(val) == hash.end() ? -1 : hash[val] ;
if (j >= 0 && i * t - j >= 0) return i * t- j ;
}
return -1 ;
}
这个回来再说,noip肯定不考
五.欧拉函数求法
欧拉函数 φ ( n ) φ(n) φ(n)表示小于或者等于 n n n的正整数中与 n n n互质的数的数目
1. φ ( 1 ) = 1 φ(1)=1 φ(1)=1
2.若 n n n是质数 p p p的 k k k次幂, φ ( n ) = φ ( p k ) = p k − p k − 1 = ( p − 1 ) ∗ p k − 1 φ(n)=φ(p^k)=p^k-p^{k-1}=(p-1)*p^{k-1} φ(n)=φ(pk)=pk−pk−1=(p−1)∗pk−1
3.由于欧拉函数是积性函数,所以若 gcd ( n , m ) = 1 \gcd(n,m)=1 gcd(n,m)=1, φ ( n m ) = φ ( n ) ∗ φ ( m ) φ(nm)=φ(n)*φ(m) φ(nm)=φ(n)∗φ(m)
4.若 n = p 1 k 1 p 2 k 2 . . . p r k r n=p_1^{k_1}p_2^{k_2}...p_r^{k_r} n=p1k1p2k2...prkr, φ ( n ) = n ∗ ∏ p ∣ n ( p − 1 p ) φ(n)=n*\prod\limits_{p|n}(\frac{p-1}{p}) φ(n)=n∗p∣n∏(pp−1)
欧拉函数一些性质:
1.当 n n n为奇数时, φ ( 2 ∗ n ) = φ ( n ) φ(2 * n) = φ(n) φ(2∗n)=φ(n)
2.当 p p p是质数时,且 n n n被 p p p整除,若 p p p与 n / p n/p n/p不互质, φ ( n ) = φ ( n / p ) ∗ p φ(n)=φ(n/p)*p φ(n)=φ(n/p)∗p
3.当 p p p是质数时,且 n n n被 p p p整除,若 p p p与 n / p n/p n/p互质, φ ( n ) = φ ( n / p ) ∗ ( p − 1 ) φ(n)=φ(n/p)*(p-1) φ(n)=φ(n/p)∗(p−1)
4. ∑ d ∣ n φ ( d ) = n \sum_{d|n}φ(d)=n ∑d∣nφ(d)=n
5.如果p为质数, φ ( p ) = p − 1 φ(p)=p-1 φ(p)=p−1
1.求单个欧拉函数
int Euler(int x) {
int res = x ;
for (int i = 2; i <= sqrt(x); i++)
if (x % i == 0){
res = res / i * (i - 1) ;
while (x % i == 0) x /= i ;
}
if (x > 1) res = res / x * (x - 1) ;//先除再乘
return res ;
}
以上算法是O(sqrt(n))的
可以通过筛质数将算法优化到O(logn)级别:
int Euler(int x) {
int res = x ;
for (int i = 1; i <= tot && prime[i] * prime[i] <= x; i++)
if (x % prime[i] == 0) {
res = res / prime[i] * (prime[i] - 1) ;
while (x % prime[i] == 0) x /= prime[i] ;
}
if (x > 1) res = res / x * (x - 1) ;
return res ;
}
init() // 调用筛质因数
2.欧拉函数 — 普通筛法
void Euler(int n) {
for (int i = 1; i <= n; i++) phi[i] = i ;
for (int i = 1; i <= n; i++)
if (phi[i] == i){
for (int j = 2; i * j <= n; j++) phi[i * j] = phi[i * j] / i * (i - 1) ;
}
}
3.欧拉函数 — 线性筛法
void Euler(int n) {
phi[1] = 1 ;
tot = 0 ;
for (int i = 2; i <= n; i++) {
if (!flag[i]) {
prime[++tot] = i ;
phi[i] = i - 1 ;
}
for (int j = 1; j <= tot; j++) {
if (i * prime[j] > n) break ;
flag[i * prime[j]] = 1 ;
if (i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * prime[j] ;//性质2
break ;
}
phi[i * prime[j]] = phi[i] * (prime[j] - 1) ;//性质3
}
}
}
六.组合数学
组合数计算 — 杨辉三角
void initc(int n, int p) {
f[0][0] = 1 ;
for (int i = 1; i <= n; i++) {
f[i][0] = 1 ;
for (int j = 1; j <= n; j++)
f[i][j] = (f[i - 1][j] + f[i - 1][j - 1]) % p ;
}
}
C [ i ] [ j ] = C [ i − 1 ] [ j ] + C [ i − 1 ] [ j − 1 ] C[i][j]=C[i-1][j]+C[i-1][j-1] C[i][j]=C[i−1][j]+C[i−1][j−1]
组合数计算 — 阶乘+乘法逆元
void init(int n, int p) {
fac[1] = inv[1] = sum[1] = 1 ;
for (int i = 2; i <= n; i++) {
fac[i] = fac[i - 1] * i % p ;
inv[i] = (p - p / i) * inv[p % i] % p ;
sum[i] = sum[i - 1] * inv[i] % p ; // sum[i] 表示前i个乘法逆元的乘积
}
}
ll C(ll a, ll b) { // C(a, b) = a!/(b!*(a-b)!)
return fac[a] * sum[b] % p * sum[a - b] % p ;
}
根据组合数的计算式得出
C n m = n ! m ! n − m ! C_n^m = \frac{n!}{m!{n-m}!} Cnm=m!n−m!n!
套用阶乘和乘法逆元求得即可
Lucas
ll Lucas(ll n, ll m) {
if (m == 0) return 1 ;
return C(n % p, m % p) * Lucas(n / p, m / p) % p ; // C计算用阶乘+逆元的那个
}
这个回来再填坑