数论模板总结

数论模板总结

作者: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(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)的非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=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(nloglogn)O(nloglogn)O(nloglogn)
iii为素数,i∗ji*jij肯定为合数

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&#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.求解线性方程

运用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&#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(mod p),a≡b(mod \ p),ab(mod p),不一定满足a/c≡b/c(mod p)a/c≡b/c(mod \ p)a/cb/c(mod p)

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

a∗x≡1(mod b)a*x≡1(mod\ b)ax1(mod b)a,ba,ba,b互质,则称xxxaaa的乘法逆元,即为a−1a^{-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(mod p)1^{-1}=1(mod \ p)11=1(mod p)

然后设p=k∗i+r,r&lt;i,1&lt;i&lt;pp=k*i+r,r&lt;i,1&lt;i&lt;pp=ki+r,r<i,1<i<p

把上式放入mod pmod \ pmod p意义下就会发现

k∗i+r≡0(mod p)k*i+r≡0(mod \ p)ki+r0(mod p)

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

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

i−1=−k∗r−1(mod p)i^{-1}=-k*r^{-1}(mod \ p)i1=kr1(mod p)

i−1=−[pi]∗(p mod i)−1(mod p)i^{-1}=-\left[\dfrac{p}{i}\right]*(p \ mod \ i)^{-1}(mod \ p)i1=[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]

−−&gt;inv[i]=(p−p/i)∗inv[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=ap−2a^{-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≡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}xa1(mod m1)xa2(mod m2)...xan(mod mn)

若保证m1,m2,...,mnm_1,m_2,...,m_nm1,m2,...,mn互质,S=∏i=1nmiS=\prod\limits_{i=1}^nm_iS=i=1nmi,Mi=SmiM_i=\frac{S}{m_i}Mi=miS,tit_iti是线性同余方程Miti≡1(mod mi)M_it_i≡1(mod \ m_i)Miti1(mod mi)的一组解

在保证有节的情况下

ans=∑i=1naiMitians = \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)表示小于或者等于nnn的正整数中与nnn互质的数的数目

1.φ(1)=1φ(1)=1φ(1)=1

2.若nnn是质数pppkkk次幂, φ(n)=φ(pk)=pk−pk−1=(p−1)∗pk−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)=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)=npn(pp1)

欧拉函数一些性质:

1.当nnn为奇数时,φ(2∗n)=φ(n)φ(2 * n) = φ(n)φ(2n)=φ(n)

2.当ppp是质数时,且nnnppp整除,若pppn/pn/pn/p不互质,φ(n)=φ(n/p)∗pφ(n)=φ(n/p)*pφ(n)=φ(n/p)p

3.当ppp是质数时,且nnnppp整除,若pppn/pn/pn/p互质,φ(n)=φ(n/p)∗(p−1)φ(n)=φ(n/p)*(p-1)φ(n)=φ(n/p)(p1)

4.∑d∣nφ(d)=n\sum_{d|n}φ(d)=ndnφ(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 ;
}

根据组合数的计算式得出

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

这个回来再填坑

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值