数论模板总结

数论模板总结

作者: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(xy,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=253

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 ij肯定为合数

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&#x27;+by&#x27;=\gcd(a,b) ax+by=gcd(a,b)的一对特解 ( x ′ , y ′ ) (x&#x27;,y&#x27;) (x,y)
x = x ′ ∗ a / gcd ⁡ ( a , b ) , y = y ′ ∗ b / gcd ⁡ ( a , b ) x=x&#x27;*a/\gcd(a,b),y=y&#x27;*b/\gcd(a,b) x=xa/gcd(a,b),y=yb/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+kb/gcd,Y=yka/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&#x27;x+b&#x27;y=\gcd(a,b) ax+by=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), ab(mod p),不一定满足 a / c ≡ b / c ( m o d   p ) a/c≡b/c(mod \ p) a/cb/c(mod p)

这种时候可以运用乘法逆元

a ∗ x ≡ 1 ( m o d   b ) a*x≡1(mod\ b) ax1(mod b) a , b a,b a,b互质,则称 x x x a a a的乘法逆元,即为 a − 1 a^{-1} a1

乘法逆元有如下几种求解方式:

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) 11=1(mod p)

然后设 p = k ∗ i + r , r &lt; i , 1 &lt; i &lt; p p=k*i+r,r&lt;i,1&lt;i&lt;p p=ki+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) ki+r0(mod p)

同时乘以 i − 1 i^{-1} i1, r − 1 r^{-1} r1就会得到

k ∗ r − 1 + i − 1 ≡ 0 ( m o d   p ) k*r^{-1}+i^{-1}≡0(mod \ p) kr1+i10(mod p)

i − 1 = − k ∗ r − 1 ( m o d   p ) i^{-1}=-k*r^{-1}(mod \ p) i1=kr1(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) i1=[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]

− − &gt; i n v [ i ] = ( p − p / i ) ∗ i n v [ p % i ] --&gt; inv[i] = (p - p / i) * inv[p \% i] >inv[i]=(pp/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} a1=ap2

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} xa1(mod m1)xa2(mod m2)...xan(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=1nmi, 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) Miti1(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=1naiMiti

且仅有一组解

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)=pkpk1=(p1)pk1

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)=npn(pp1)

欧拉函数一些性质:

1.当 n n n为奇数时, φ ( 2 ∗ n ) = φ ( n ) φ(2 * n) = φ(n) φ(2n)=φ(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)(p1)

4. ∑ d ∣ n φ ( d ) = n \sum_{d|n}φ(d)=n dnφ(d)=n

5.如果p为质数, φ ( p ) = p − 1 φ(p)=p-1 φ(p)=p1

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[i1][j]+C[i1][j1]

组合数计算 — 阶乘+乘法逆元

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!nm!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计算用阶乘+逆元的那个
}

这个回来再填坑

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值