数论模板总结
作者: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(logb)O(logb)O(logb)
就是按222进制分解,按位如果当前位为111就乘上相应的权值,权值可以递推的求,而不用于处理
二.最大公约数(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=yx = yx=y, gcd(x,y)=x\gcd(x,y)=xgcd(x,y)=x,否则
1)x,yx,yx,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)xxx为偶数,yyy为奇数,gcd(x,y)=gcd(x/2,y);\gcd(x,y)=\gcd(x/2,y);gcd(x,y)=gcd(x/2,y);
3)xxx为奇数,yyy为偶数,gcd(x,y)=gcd(x,y/2);\gcd(x,y)=\gcd(x,y/2);gcd(x,y)=gcd(x,y/2);
4)x,yx,yx,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)的非111正整数整除的数都不是质数
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)的数枚举是不是xxx的因数,如果是,应当记录其个数
最后特判一个大于(x)\sqrt(x)(x)的大因数,比如106=2∗53106=2*53106=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(nloglogn)O(nloglogn)O(nloglogn)
若iii为素数,i∗ji*ji∗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 ;
}
求解形如ax+by=cax+by=cax+by=c的方程
如果c%gcd(a,b)!=0c\%\gcd(a,b)!=0c%gcd(a,b)!=0无解
求解出ax′+by′=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.求解线性方程
运用exgcdexgcdexgcd可以求出形如ax+by=cax+by=cax+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(mod p),a≡b(mod \ p),a≡b(mod p),不一定满足a/c≡b/c(mod p)a/c≡b/c(mod \ p)a/c≡b/c(mod p)
这种时候可以运用乘法逆元
若a∗x≡1(mod b)a*x≡1(mod\ b)a∗x≡1(mod b),a,ba,ba,b互质,则称xxx为aaa的乘法逆元,即为a−1a^{-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(mod p)1^{-1}=1(mod \ p)1−1=1(mod p)
然后设p=k∗i+r,r<i,1<i<pp=k*i+r,r<i,1<i<pp=k∗i+r,r<i,1<i<p
把上式放入mod pmod \ pmod p意义下就会发现
k∗i+r≡0(mod p)k*i+r≡0(mod \ p)k∗i+r≡0(mod p)
同时乘以i−1i^{-1}i−1,r−1r^{-1}r−1就会得到
k∗r−1+i−1≡0(mod p)k*r^{-1}+i^{-1}≡0(mod \ p)k∗r−1+i−1≡0(mod p)
i−1=−k∗r−1(mod p)i^{-1}=-k*r^{-1}(mod \ p)i−1=−k∗r−1(mod p)
i−1=−[pi]∗(p mod i)−1(mod p)i^{-1}=-\left[\dfrac{p}{i}\right]*(p \ mod \ i)^{-1}(mod \ p)i−1=−[ip]∗(p mod i)−1(mod p)
于是上述我们可以推出逆元了
inv[i]=−(p/i)∗inv[p%i]inv[i] = -(p/i) * inv[p \% i]inv[i]=−(p/i)∗inv[p%i]
−−>inv[i]=(p−p/i)∗inv[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=ap−2a^{-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≡a1(mod m1)x≡a2(mod m2)...x≡an(mod mn)\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)
若保证m1,m2,...,mnm_1,m_2,...,m_nm1,m2,...,mn互质,S=∏i=1nmiS=\prod\limits_{i=1}^nm_iS=i=1∏nmi,Mi=SmiM_i=\frac{S}{m_i}Mi=miS,tit_iti是线性同余方程Miti≡1(mod mi)M_it_i≡1(mod \ m_i)Miti≡1(mod mi)的一组解
在保证有节的情况下
ans=∑i=1naiMitians = \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)表示小于或者等于nnn的正整数中与nnn互质的数的数目
1.φ(1)=1φ(1)=1φ(1)=1
2.若nnn是质数ppp的kkk次幂, φ(n)=φ(pk)=pk−pk−1=(p−1)∗pk−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)=1gcd(n,m)=1,φ(nm)=φ(n)∗φ(m)φ(nm)=φ(n)*φ(m)φ(nm)=φ(n)∗φ(m)
4.若n=p1k1p2k2...prkrn=p_1^{k_1}p_2^{k_2}...p_r^{k_r}n=p1k1p2k2...prkr,φ(n)=n∗∏p∣n(p−1p)φ(n)=n*\prod\limits_{p|n}(\frac{p-1}{p})φ(n)=n∗p∣n∏(pp−1)
欧拉函数一些性质:
1.当nnn为奇数时,φ(2∗n)=φ(n)φ(2 * n) = φ(n)φ(2∗n)=φ(n)
2.当ppp是质数时,且nnn被ppp整除,若ppp与n/pn/pn/p不互质,φ(n)=φ(n/p)∗pφ(n)=φ(n/p)*pφ(n)=φ(n/p)∗p
3.当ppp是质数时,且nnn被ppp整除,若ppp与n/pn/pn/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 ;
}
根据组合数的计算式得出
Cnm=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计算用阶乘+逆元的那个
}
这个回来再填坑