【数学 进阶】初等数论 学习笔记

0x00 整除

重点不多,主要集中在几点上:

1.整除符号:
a ∣ b a | b ab
表示,b可以被a整除。
记忆方法:b >= a(像分母一样)

2. a | b 且 b | c,则a | (b * x + c * y)

3. a * x + b + y = 1 且 a | n , b | n,则 (a + b) | n

4. b = 任何整数 * d + c,则d | b 的充要条件为d | c

5. 2 能整除 a 的末 1 位,则 2 | a
4 能整除 a 的末 2 位,则 4 | a
8 能整除 a 的末 3 位,则 8 | a
3 能整除 a 的各位数字和,则 3 | a
9 能整除 a 的各位数字和,则 9 | a
11 能整除 a 的偶位数字和与奇位数字之和的差,则 11 | a
7或11或13整除a的末三位数字所组成的数与末三位以前的数字所组成的数的差(大数减小数),则7或11或13 | a

0x10 同余

a ≡ b ( m o d m ) a\equiv b\pmod{m} ab(modm)
表示 a 与 b 关于 m 同余


a ≡ b ( m o d m ) a\equiv b\pmod{m} ab(modm) b ≡ c ( m o d m ) b\equiv c\pmod{m} bc(modm)
a ≡ c ( m o d m ) a\equiv c\pmod{m} ac(modm)


a ≡ b ( m o d m ) a\equiv b\pmod{m} ab(modm) a ∗ c ≡ b ∗ c ( m o d m ) a*c\equiv b*c\pmod{m} acbc(modm)
a ≡ b ( m o d m ) a\equiv b\pmod{m} ab(modm) , c ≡ d ( m o d m ) c\equiv d\pmod{m} cd(modm) a ∗ c ≡ b ∗ d ( m o d m ) a*c\equiv b*d\pmod{m} acbd(modm)


a ≡ b ( m o d m ) a\equiv b\pmod{m} ab(modm) a n ≡ b n ( m o d m ) a^n\equiv b^n\pmod{m} anbn(modm)


a ∗ b ( m o d m ) a* b\pmod{m} ab(modm) == ( a ∗ m ) ∗ ( b ∗ m ) ( m o d m ) (a * m) * (b * m) \pmod{m} (am)(bm)(modm)
a m o d    p = x a \mod p = x amodp=x a m o d    q = x a\mod q = x amodq=x 则 p , q 互质

本质: a ≡ b ( m o d m ) a\equiv b\pmod{m} ab(modm) => a - b = m * k

0x20 最大公约数

0x21 辗转相除法(欧几里得法)

inline int gcd(int x , int y){
	return y ? gcd(y , x % y) : x ;
}

0x22 最小公倍数

gcd(a , b) * lcm(a , b) = a * b ;

逆推一下即可。

0x23 拓展欧几里得定理

目的: 已知 a , b , 求一组 p , q 使得
p ∗ a + q ∗ b = g c d ( a , b ) p * a+q*b=gcd(a , b) pa+qb=gcd(a,b)

#include<iostream>
#include<cstdio>
using namespace std ;

int num1 ,num2 ;

int fir ,sec ;
inline int extended_gcd(int a , int b){
	int rep ,tmp ;
	if(b == 0){
		fir = 1 ;
		sec = 0 ;
		return a ;
	}
	rep = extended_gcd(b , a % b) ;
	tmp = fir ;
	fir = sec ;
	sec = tmp - a / b * sec ;
	return rep ;
}

int main(){
	scanf("%d%d" , &num1 , &num2) ;
	int ans = extended_gcd(num1 , num2) ;
	printf("%d * %d + %d * %d = %d" , num1 , fir , num2 , sec , ans) ;
	return 0 ;
}

0x24 求解线性同余方程

首先我们要明确一件事情:针对
a ∗ x + b ∗ y = c a*x+b*y=c ax+by=c
等价于
a ∗ x ≡ c ( m o d b ) a*x\equiv c\pmod{b} axc(modb)
充要条件:
c m o d    g c d ( a , b ) = 0 c \mod{gcd(a , b)} = 0 cmodgcd(a,b)=0


解法:

  1. 首先,找到一组 a * x0 + b * y0 = gcd(x0 , y0)
  2. 变式:
    a ∗ x 0 ∗ c g c d ( x 0 , y 0 ) + b ∗ y 0 ∗ c g c d ( x 0 , y 0 ) = c \frac{a * x_0 * c}{gcd(x_0 , y_0)} + \frac{b * y_0 * c}{gcd(x_0 , y_0)} = c gcd(x0,y0)ax0c+gcd(x0,y0)by0c=c
    此时
    a ∗ x 0 ∗ c g c d ( x 0 , y 0 ) + b ∗ y 0 ∗ c g c d ( x 0 , y 0 ) = c a*\frac{x_0 * c}{gcd(x_0 , y_0)}+b* \frac{ y_0 * c}{gcd(x_0 , y_0)}=c agcd(x0,y0)x0c+bgcd(x0,y0)y0c=c
    a和b的两个系数分别是x , y的解。

若 gcd(a , b) = 1 , 且x0 , y0 为 a x + b y = c的 1 组解,则
{ x = x 0 + b ∗ t y = y 0 − a ∗ t \begin{cases}x = x_0 + b*t\\y=y_0-a*t\end{cases} {x=x0+bty=y0at
目的:求最小整数解
t = b gcd ⁡ ( a , b ) t=\frac{b}{\gcd(a ,b)} t=gcd(a,b)b
记得
x = ( x m o d    t + t ) m o d    t x = (x \mod t + t)\mod{t} x=(xmodt+t)modt
(为了防负数)

0x30 逆元

“除以一个数再取模等同于乘以这个数的逆元再取模 ”

这就是逆元的本质。具体的 code:

#include<iostream>
#include<cstdio>
using namespace std ;

int a ;
const int MOD = 1000007 ;

//拓展欧几里得求逆元
//时间复杂度:O(logn)
//特点:只要存在逆元即可求,适用于个数不多但是mod很大的时候,也是最常见的一种求逆元的方法
int fir ;
int sec ;
inline int exgcd(int a , int b){
	if(b == 0){
		fir = 1 ;
		sec = 0 ;
		return a ;
	}
	int ans = exgcd(b , a%b) ;
	int cmp = fir ;
	fir = sec ;
	sec = cmp - a / b * sec ;
	return ans ;
}
inline int inv_gcd(int a , int mod){//求a在mod下的逆元(不存在返回-1)
	int d = exgcd(a , mod) ;
	return d == 1 ? (fir % mod + mod) % mod : -1 ;
}

//费马小定理、欧拉定理求逆元(蒙哥马利快速幂求逆元)
//时间复杂度:O(logmod)
//一般在mod是个素数的时候用,比扩欧快一点。但是如果是合数,相信一般没人无聊到去算个欧拉函数。
inline int qpow(int base , int p , int mod){
	int res = 1 ;
	while(p){
		if(p & 1)
			res = (res % mod) * base % mod ;
		base = (base % mod) * base % mod ;
		p >>= 1 ;
	}
	return res ;
}
inline int inv_fermat(int a , int mod){
	return qpow(a , mod-2 , mod) ;
}

//线性求逆元
//时间复杂度:O(n)
//调用前要先预处理、调用的时候要先对除数取mod;mod数是不大的素数而且多次调用,比如卢卡斯定理。
int invnum[MOD + 5] ;
inline void inv(int mod){
	invnum[1] = 1 ;
	for(int i = 2;i < mod;++i)//注意开long long
		invnum[i] = (mod - mod / i) * invnum[mod % i] % mod ;
} 

//递归求逆元
//时间复杂度:O(logmod)
//好像找到了最简单的算法了!!
//适用范围: mod数是素数,所以并不好用,比如中国剩余定理中就不好使
//因为很多时候可能会忘记考虑mod数是不是素数。
inline int inv_dp(int i , int mod){
	if(i == 1)
		return 1 ;
	return (mod - mod / i) * inv_dp(mod % i , mod) % mod ;
}

int main(){
	int mod ;
	scanf("%d%d" , &a , &mod) ;
	int ans1 = inv_gcd(a , mod) ;
	cout << ans1 << '\n' ;
	
	int ans2 = inv_fermat(a , mod) ;
	cout << ans2 << '\n' ;
	
	inv(mod) ;
	cout << invnum[a] << '\n' ;
	
	int ans4 = inv_dp(a , mod) ;
	cout << ans4 << '\n' ;
	return 0 ;
}

材料记录

0x40 中国剩余定理

解决问题:线性同余方程组
将下面的方程组进行分解:
在这里插入图片描述
基于每一项提取系数并拆分得出 k 个方程组:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这些方程组都要在乘上一个系数才是真正的答案。
code:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std ;

const int MAXN = 20 ;

long long n ;
long long mod[MAXN] ;
long long num[MAXN] ;

inline long long exgcd(long long a , long long b , long long &x , long long &y){
	if(b == 0){
		x = 1 ;
		y = 0 ;
		return a ;
	}
	long long gcd = exgcd(b , a%b , x , y) ;
	long long t = x ;
	x = y ;
	y = t - a / b * y ;
	return gcd ;
}

inline long long CRT(){
	long long x ,y ;
	long long ans = 0 ;
	long long sum = 1 ;
	long long m = 0 ;
	for(long long i = 1;i <= n;++i){
		sum *= mod[i] ;
	}
	for(long long i = 1;i <= n;++i){
		m = sum / mod[i] ;//拆分每个方程组,m是拆下来后剩下的其他值
		exgcd(mod[i] , m , x , y) ;//求解单个线性方程组
		ans = (ans + y * m * num[i]) % sum ;
	}
	
	return ans > 0 ? ans : ans + sum ;//负数取模
}

int main(){
	scanf("%lld" , &n) ;
	for(long long i = 1;i <= n;++i){
		scanf("%lld%lld" , mod+i , num+i) ;
	}
	printf("%lld" , CRT()) ;
	return 0 ;
}

0x50 斐波那契数

斐波那契通项公式:
在这里插入图片描述
其他的部分会单独一篇blog详细解读。

0x60 卡特兰数

卡特兰数基本通项公式:
f n = 1 n + 1 C 2 n n f_n =\frac{1}{n+1}C_{2n}^n fn=n+11C2nn
为什么是基本公式?因为不同的题会对卡特兰数的计算产生不同的影响,虽然不同但不会产生大的变化。
基本特征:求方案数、对方案数产生一定限制。
具体例子会单独出一篇 blog 解释。

0x70 欧拉函数

0x71 欧拉函数的定义

定义:欧拉函数φ(n)的值等于序列0,1,2,…,n-1中与n互素的数的个数。
友情提示: φ ( p h i ˋ ) φ(ph\grave i) φphiˋ
特别的,φ(1) = 1(和1互质的数(小于等于1)就是1本身)。
性质:

  1. 欧拉函数 φ(n * m) = φ(n) * φ(m)
    (判定条件:n、m 互质)
  2. 对于 质数 p,它的 欧拉函数φ(p)= p - 1
  3. 对于 奇数 n,φ(2 * n)= φ(n)
  4. 当 n = pk 时 ,φ(n)= pk - pk-1
  5. n 中 与 n 互质的数的和为 φ ( n ) 2 ∗ n \frac{φ(n)}{2 * n} 2nφn(n > 1)
  6. φ ( n ) ( n > 2 ) φ(n)(n > 2) φn(n>2)的值为偶数
  7. p ∣ n 且 p 2 ∣ n ,则 φ ( n ) = φ ( n p ) ∗ p p\mid n且p^2\mid n,则 φ(n)=φ(\frac{n}{p})*p pnp2n,则φ(n)=φ(pn)p
  8. p ∣ n 且 p 2 ∤ n ,则 φ ( n ) = φ ( n p ) ∗ ( p − 1 ) p\mid n且p^2\nmid n,则 φ(n)=φ(\frac{n}{p})*(p-1) pnp2n,则φ(n)=φ(pn)(p1)

0x72 欧拉函数的线性筛法

该算法在可在线性时间内筛素数的同时求出所有数的欧拉函数。
需要用到如下性质(p为质数):

  1. φ ( p ) = p - 1 因为质数p除了1以外的因数只有p,故1至p的整数只有p与p不互质

  2. 如果i mod p = 0, 那么 φ(i * p) = p * φ(i)

  3. 若i mod p ≠0, 那么phi(i * p)=phi(i) * (p-1)
    [i mod p 不为0且p为质数, 所以i与p互质, 那么根据欧拉函数的积性phi(i * p)=phi(i) * phi( p ) 其中phi( p )=p-1即第一条性质]

#include<iostream>
#include<cstdio>
using namespace std ;

const int MAXN = 1e6 ;

int phi[MAXN] ;
int prime[MAXN] ;
int tot ;
int ans = 0 ;
bool mark[MAXN] ;
int n ;

inline void get_phi(){
	phi[1] = 1 ;
	
	for(int i = 2;i <= n;++i){
		if(mark[i] == false){
			prime[++tot] = i ;
			phi[i] = i - 1 ;
		}
		for(int j = 1;j <= tot;++j){
			if(i * prime[j] > MAXN)
				break ;
			mark[i * prime[j]] = true ;
			if(i % prime[j] == 0){
				phi[i * prime[j]] = phi[i] * prime[j] ;
				break ;
			}else
				phi[i * prime[j]] = phi[i] * (prime[j] - 1) ;
		}
	}
}

int main(){
	scanf("%d" , &n) ;
	get_phi() ;
	for(int i = 1;i <= n;++i){
		if(i % 11 == 0)
			printf("\n") ;
		printf("%d " , phi[i]) ;
	}
	return 0 ;
}

0x73 欧拉函数的使用

RSA算法

自己看吧考试又不考RSA算法详解

欧拉函数对其他算法的优化
利用欧拉函数的积性函数 特性进行优化。
*注:积性函数:积性函数指对于所有 互质 的整数a和b有性质 f (ab) = f (a) * f (b)的 数论函数 。
证明:
∑ d ∣ n φ ( d ) = n \sum\limits_{d\mid n}φ (d)=n dnφ(d)=n


参考资料:欧拉函数
欧拉函数的性质及证明

0x80 素数

0x81 威尔逊定理

若 p 为素数,则:
( p − 1 ) ! ≡ − 1 ( m o d p ) (p-1)! \equiv-1\pmod{p} (p1)!1(modp)
值得注意的是,威尔逊逆定理依旧成立。
证明很简单,这里不给出详细证明。

0x82 费马小定理

若 p 为素数,a为正整数,且 a ,p 互质,则:
a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv1\pmod{p} ap11(modp)
将化简,可得:
a p ≡ a ( m o d p ) a^{p}\equiv a\pmod{p} apa(modp)
这就是 费马小定理。

0x83 埃式筛法&线性筛法

埃式筛法 是一种近似于暴力的枚举方法,将比他大的质数倍的数累加起来判断最大值。
线性筛法 利用最大的因子筛掉了大部分的重复计算,去掉了多余的操作。

#include<iostream>
#include<cstdio>
using namespace std ;

const int MAXN = 1e6 ;

long long n ;

inline long long read(){
	long long x = 0 ;
	long long y = 1 ;
	char c = getchar() ;
	while(c < '0' || c > '9'){
		if(c == '-')
			y = -1 ;
		c = getchar() ;
	}
	while(c <= '9' && c >= '0'){
		x = x * 10 + c - '0' ;
		c = getchar() ;
	}
	return x * y ;
}

//埃式筛法——特点:空间小、码量少 [O(n * log log n)]
bool vis1[MAXN] ;
inline void Eratosthenes_primes(long long num){
	memset(vis1 , 0 , sizeof(vis1)) ;
	
	for(long long i = 2;i <= num;++i){
		if(vis1[i] == true)
			continue ;
		printf("%lld " , i) ;
		for(long long j = i;j <= num / i;++j)
			vis1[i * j] = true ;
	}
}

//线性筛法——特点:时间快[O(n)]
long long prime[MAXN] ;
long long vis2[MAXN] ;
long long cnt = 0 ;
inline void primes(long long num){
	memset(vis2 , 0 , sizeof(vis2)) ;//最小质因子
	cnt = 0 ;//质数数量
	for(long long i = 2;i <= num;++i){
		if(vis2[i] == 0){
			vis2[i] = i ;
			prime[++cnt] = i ;
		}
		//给当前质数加上质因子
		for(long long j = 1;j <= cnt;++j){
		//i有比prime[j]更小的质因子,或者超出范围,停止循环
			if(prime[j] > vis2[i] || prime[j] > num / i)
				break ;
			//prime[j]是合数vis2[i * prime[j]]的最小质因子
			vis2[i * prime[j]] = prime[j] ;
		}
	}
	
	for(int i = 1;i <= cnt;++i)
		printf("%lld " , prime[i]) ;
}
int main(){
	n = read() ;
	
	Eratosthenes_primes(n) ;
	
	printf("\n") ;
	
	primes(n) ;
	
	return 0 ;
}

0x84 Millar-Rabin 素数测试

利用 费马小定理 反证素数

//时间复杂度:O(log n)
#include<iostream>
#include<cstdlib>
using namespace std ;

long long n ;
inline long long read(){
	long long x = 0 ,y = 1 ;
	char c = getchar() ;
	while(c < '0' || c > '9'){
		if(c == '-')
			y = -1 ;
		c = getchar() ;
	}
	while(c >= '0' && c <= '9'){
		x = x * 10 + c - '0' ;
		c = getchar() ;
	}
	return x * y ;
}

//快速幂
inline long long quick_pow(long long base , long long up , long long mod){
	long long res = 1 ;
	while(up){
		if(up & 1)
			res = (res % mod * base) % mod ;
		base = (base % mod) * base % mod ;
		up >>= 1 ;
	}
	return res ;
}

inline bool Miller_Robbin(long long num){
	if(num == 0 || num == 1)
		return false ;
	if(num == 2)
		return true ;
	
	for(int i = 1;i <= 30;++i){//枚举30次,保证正确率
		int base = rand() % (num - 1) + 1 ;//生成随机枚举底数
		if(quick_pow(base , num-1 , num) != 1)//费马小定理
			return false ;
	}
	return true ;
}

int main(){
	n = read() ;
	
	if(Miller_Robbin(n) == true)
		printf("YES") ;
	else
		printf("NO") ;
	return 0 ;
}

0x85 欧拉定理

内容:
若 a 、m 互质,则 a φ ( m ) ≡ 1 ( m o d m ) a^{φ (m)}\equiv1\pmod{m} aφ(m)1(modm)
用途:
指数取模。【前提:p为质数!】
x y m o d    p x^y\mod{p} xymodp可被化简为
x y m o d    φ ( p ) m o d    p x^{y\mod{φ (p)}}\mod{p} xymodφ(p)modp

0x86 Pollard Rho 求大数因子

//洛谷例题P4718
//没完全解决
//输出最大的质因子!!!!!
//可能需要__int128
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int t;
long long max_factor, n;

long long gcd(long long a, long long b) {
	if (b == 0) return a;
	return gcd(b, a % b);
}

long long quick_pow(long long x, long long p, long long mod) {  // 快速幂
	long long ans = 1;
	while (p) {
		if (p & 1) ans = (__int128)ans * x % mod;
		x = (__int128)x * x % mod;
		p >>= 1;
	}
	return ans;
}

bool Miller_Rabin(long long p) {  // 判断素数
	if (p < 2) return 0;
	if (p == 2) return 1;
	if (p == 3) return 1;
	long long d = p - 1, r = 0;
	while (!(d & 1)) ++r, d >>= 1;  // 将d处理为奇数
	for (long long k = 0; k < 10; ++k) {
		long long a = rand() % (p - 2) + 2;
		long long x = quick_pow(a, d, p);
		if (x == 1 || x == p - 1) continue;
		for (int i = 0; i < r - 1; ++i) {
			x = (__int128)x * x % p;
			if (x == p - 1) break;
		}
		if (x != p - 1) return 0;
	}
	return 1;
}

long long Pollard_Rho(long long x) {
	long long s = 0, t = 0;
	long long c = (long long)rand() % (x - 1) + 1;
	int step = 0, goal = 1;
	long long val = 1;
	for (goal = 1;; goal *= 2, s = t, val = 1) {  // 倍增优化
		for (step = 1; step <= goal; ++step) {
			t = ((__int128)t * t + c) % x;//生成伪随机函数
			val = (__int128)val * abs(t - s) % x;
			if ((step % 127) == 0) {
				long long d = gcd(val, x);
				if (d > 1) return d;
			}
		}
		long long d = gcd(val, x);
		if (d > 1) return d;
	}
}

void fac(long long x) {
	if (x <= max_factor || x < 2) return;
	if (Miller_Rabin(x)) {              // 如果x为质数
		max_factor = max(max_factor, x);  // 更新答案
		return;
	}
	long long p = x;
	while (p >= x) p = Pollard_Rho(x);  // 使用该算法
	while ((x % p) == 0) x /= p;
	fac(x), fac(p);  // 继续向下分解x和p
}

int main() {
	scanf("%d", &t);
	while (t--) {
		srand((unsigned)time(NULL));
		max_factor = 0;
		scanf("%lld", &n);
		fac(n);
		if (max_factor == n)  // 最大的质因数即自己
			printf("Prime\n");
		else
			printf("%lld\n", max_factor);
	}
	return 0;
}

参考资料:博客园

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值