蒟蒻的数论整理

本文深入讲解数论基础算法,包括快速幂的实现原理及代码,素数筛选的埃氏筛和欧拉筛,质因数分解的详细步骤,阶乘的质因数分解,最大公约数与最小公倍数的计算,以及逆元的概念和计算方法。此外,还介绍了中国剩余定理的应用,为解决同余方程提供理论支持。内容覆盖了基础数论算法及其在编程竞赛中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数论的水不深,但是你把握不住

1.快速幂

思路:

如果 b = c + d b=c+d b=c+d
那么我们可以将 a b a^b ab转化为 a c ∗ a d a^c*a^d acad 小学生都会的公式
在代码中我们可以在for循环中设一个临时变量i,用来记 a i a^i ai并判断 i i i是否在 b b b中,如果是将答案乘上 a i a^i ai即可,同时b减去i

原理:讲指数拆成多个指数(过程中转化为二进制进行计算)
代码:
int pow(int a,int b){//此快速幂代码可能和一些大佬学的不太一样
	if(b&1){//首先判断1是否在b中
		res=a;
		b--;
	}
	ll tt=a*a;
	for(ll i=2;b;i*=2){
		ll kt=b|i;//或运算的优先级比等于低,如果放在下方的判断语句可能会出错
		if(kt==b){
			res=(res*tt);
			b^=i;//也可以写成b-=i;
		}
		tt=(tt*tt);
	}
}

2.素数筛

作用:

求出一段区间内所有的素数

埃氏筛

思路:

将一个素数的所有倍数进行标记(即为合数)
时间复杂度约为 O ( n l o g n ) O(n log n) O(nlogn)
这个思路比较简单,直接上代码

代码:
void get_primes1(int n) {//埃氏筛
	memset(prime, false, sizeof prime);
	cnt = 0;
	for (int i = 2; i <= n; i++) {
		if (!prime[i]) {
			primes[cnt++] = i;
			for (int j = i; j <= n / i; j++) 
				prime[j * i] = true;
		}
	}
}

欧拉筛

思路:

这个算法中我们设一个记录素数的变量primes,记录下素数后再标记primes数组中每个素数的倍数(即为合数)。

void get_primes2(int n) {//欧拉筛(不完整代码)
	memset(prime, false, sizeof prime);
	cnt = 0;
	for (int i = 2; i <= n; i++) {
		if (!prime[i]) primes[cnt++] = i;
		for (int j = 0; primes[j] <= n / i; j++) {
			prime[i * primes[j]] = true;
		}
	}
}

为了对其进行优化,我们在标记合数循环中添加了一个判断语句:

if (i % primes[j] == 0) break;

这个判断语句其实就是判断primes[j]是否在i中如果在i中证明i中已经包含了接下来的数的最小质因子(即为primes[j]),但是继续往下循环primes[j]会不断变大,那么就没有循环的必要了,就要退出循环。

这里我们说了primes[j]为i中的质因子所以i × \times ×primes[j+k]可以用后期循环和primes[j]来标记

所以这个代码几乎每个数只跑过一遍,时间复杂度为: O ( n ) O(n) O(n)

完整代码:
void get_primes2(int n) {//欧拉筛
	memset(prime, false, sizeof prime);
	cnt = 0;
	for (int i = 2; i <= n; i++) {
		if (!prime[i]) primes[cnt++] = i;
		for (int j = 0; primes[j] <= n / i; j++) {
			prime[i * primes[j]] = true;
			if (i % primes[j] == 0) break;
		}
	}
}

3.质因数分解

思路:

外层循环判断合数n中是否有 i i i这个质因数,若有就标记它。
但是 n n n中可能会有多个 i i i,所以我们就要进行一个循环直到 n n n没法整除i为止,这个思路比较简单,直接上代码

代码:
pair<int, int> factor[N];
int idx;
void divide(int n) {//质因数分解
	idx = 0;
	for (int i = 2; i <= n / i; i++) {
		if (n % i == 0) {
			int k = 0;
			while (n % i == 0) k++, n /= i;
			factor[idx++] = {i, k};
		}
	}
	if (n > 1) factor[idx++] = {n, 1};
}

4.阶乘 n ! n! n!的质因数分解

n!=1 * 2 * 3*…(n-1)*n

思路:

我们首先用欧拉筛筛出所有 n n n范围内的素数记录到primes数组中。
现在, n ! n! n!因数中(指的是1到n)有的数可以分解为x个有的只能分解为x-1个,那么我们就每次除一边,来计算这个值,思路较简单,直接上代码

代码:
void divide_fac(int n) {//阶乘n!的质因数分解
	get_primes2(n);
	idx = 0;
	for (int i = 0; i < cnt; i++) {
		int p = primes[i];
		int s = 0;
		for (int j = n; j; j /= p) s += j / p;
		factor[idx++] = {p, s};
	}
}

5.最大公约数与最小公倍数

辗转相除法求解

此算法较简单直接上代码

最大公约数:
int GCD(int a, int b)
{
	int c;
	while (b > 0)
	{
		c = a % b;
		a = b;
		b = c;
	}
	return a;
}
最小公倍数:

两数相乘然后除以最大公约数即为最小公倍数

int LCM(int a,int b)
{
	int c;
	c = a * b / GCD(a, b);
	return c;
}

特殊定理:存在A,B gcd(A,B)=x A=ax B=bx lcm(A,B)=xab a与b互质

那么 AB=ab*x2=gcd(A,B)lcm(A,B)
推导过程
假设2数为A,B 可以拆分成A=ax ;
B=bx a&b互质 那么,AB的最小公倍数为abx AB 的最大公约数为 x2者乘起来正好等于A
B
(此过程来源于百度)

裴蜀定理

思路:

裴蜀定理说明了对任何整数 a、b和它们的最大公约数 d ,关于未知数 x以及 y 的线性的丢番图方程。
a , b a,b a,b是整数,且 g c d ( a , b ) = d gcd(a,b)=d gcd(a,b)=d,那么对于任意的整数 x , y , a x + b y x,y,ax+by x,y,ax+by都一定是d的倍数,特别地,一定存在整数 x x x, y y y,使 a x + b y = d ax+by=d ax+by=d成立。根据我们上面的gcd可以推导出在下一次gcd中a变为b,b变为a%b,即x为y,y为x%y
根据常识

a%b=a-a/b*b

可得:

bx+(a-a/b * b)y=d
bx+ay-(a/b * b)y=d
ay+[x-(a/b)*y]b=d

这我们就推出返回的 x x x就是 y y y y y y x − ( a / b ) ∗ y x-(a/b)*y x(a/b)y

代码:
ll gcd(ll a,ll b,ll &x,ll &y){
	if(!b){
		x=1;
		y=0;
		return a;
	}
	ll d=gcd(b,a%b,x,y);
	ll t=x;
	x=y;
	y=t-(a/b)*y;
	return d;
}

6.逆元

逆元是什么?

如果 n a \frac{n}{a} an m o d mod mod p p p = = = n × x n\times x n×x m o d mod mod p p p x ∈ Z x \in Z xZ
那么我们称 x m i n x_{min} xmin a a a在模 p p p意义下的逆元(记作 x m i n = a − 1 x_{min}=a^{-1} xmin=a1)

为什么a和p不互质就不存在逆元:

证:

令c=gcd(a,p)且c>1(即a,p不互质)

ax=1(mod p)

等价于:

ax+py=1

∵c|a,p

∴c|(ax+py)

∴c|1

∵c>1

所以a,p不存在逆元

证毕

做法1(费马小定理):

我们知道 a a a p p p的情况下:
a p − 1 a^{p-1} ap1 m o d mod mod p p p = = = 1 1 1 m o d mod mod p p p
费马小定理证明
那么我们可以得出:
( a × a p − 2 ) (a\times a^{p-2}) (a×ap2) m o d mod mod p p p = = = 1 1 1 m o d mod mod p p p
则b的逆元为 ( a p − 2 (a^{p-2} (ap2 m o d mod mod p ) p) p) a a a在模 p p p意义下的逆元
即可用快速幂求解

代码:
//注:根据不同的题目请自行开long long
int power(int x,int y,int p){
    int t=x;
    int ans=1,it=1;
    while(y){
        if(y&it){
            y^=it;
            ans*=t;
            ans%=p;
        }
        it<<=1;
        t*=t;
        t%=p;
    }
    return ans%p;
}
int fpm(int a,int p){//费马小定理求逆元
    return power(a,p-2,p)%p;
}

时间复杂度为: O ( l o g 2 n ) O(log_{2}n) O(log2n)

此做法由于复杂度较大,且常数较大不建议使用。

P3811 【模板】乘法逆元 在此题中最后两个点可以卡死费马小定理(注意开longlong

做法2(扩展欧几里得):

要求 a a a在模 p p p意义下的逆元我们可以将:

a x = 1 ( m o d ax=1(mod ax=1(mod p ) p) p)

转化成:

a x − p y = 1 ax-py=1 axpy=1

没错你没看错这就是上面我们讲的裴蜀定理(扩展欧几里得)

所以就按照扩展欧几里得的模板求出上面式子中的 x x x即可

而且这个做法还可以判断最大公约数是否为1(是否存在逆元)

代码:
//根据个人需要改longlong
int exgcd(int a,int b,int &x,int &y){
    if(!b){
        x=1;
        y=0;
        return a;
    }
    int d=exgcd(b,a%b,x,y);
    int t=x;
    x=y;
    y=t-(a/b)*y;
    return d;
}
int inv(int a,int p){//扩展欧几里得求逆元
    int x,y;
    int ans=exgcd(a,p,x,y);
    if(ans>1) return -1;//a和p不互质
    else return (x%p+p)%p;//防止负数的情况
}

这种求法非常高效,而且常数较小,用与求单个数的逆元非常方便。

然后你再交一下这题:P3811 【模板】乘法逆元

你会发现,最后一个点还是T,吸氧也会T,不要慌,看这么多输出就知道事情不简单,此时就要用复杂度~~(复杂度假了)~~最高的算法——递推。

做法3 递推法:

p = k i + r ; p=ki+r; p=ki+r;{ k = ⌊ p i ⌋ , r = p k=\lfloor\frac{p}{i}\rfloor,r=p k=ip,r=p m o d mod mod i i i}( i < p , k < p , r > i i<p,k<p,r>i i<p,k<p,r>i)

则有 k i + r ≡ 0 ( m o d ki+r≡0(mod ki+r0(mod p ) p) p)

①式乘 i − 1 ∗ r − 1 i^{-1}*r^{-1} i1r1得:

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

移项得:

i − 1 ≡ − k ∗ r − 1 ( m o d i^{-1}≡-k*r^{-1}(mod i1kr1(mod p ) p) p)

代入 k = ⌊ p i ⌋ , r = p k=\lfloor\frac{p}{i}\rfloor,r=p k=ip,r=p m o d mod mod i i i

i − 1 ≡ − ⌊ p i ⌋ ∗ ( p i^{-1}≡-\lfloor\frac{p}{i}\rfloor*(p i1ip(p m o d mod mod i ) − 1 ( m o d i)^{-1}(mod i)1(mod p ) p) p)

由于 ( p (p (p m o d mod mod i ) < i i)<i i)<i

所以,我们已经求出 ( p (p (p m o d mod mod i ) − 1 i)^{-1} i)1

用数组inv[i]记录 i − 1 i^{-1} i1( i i i的逆元)

则inv[i]= − ⌊ p i ⌋ ∗ i n v [ p -\lfloor\frac{p}{i}\rfloor*inv[p ipinv[p m o d mod mod i ] i] i] m o d mod mod p p p

证毕;

注:为防止出现逆元为负数在上面式子中要加上p

代码:
//AC_664ms
#include<cstdio>
#include<iostream>
#define ll long long
using namespace std;
const int maxn=3e6+5;
ll inv[maxn]={0,1};
int main(){
   int n,p;
   scanf("%d%d",&n,&p);
   printf("1\n");
   for(int i=2;i<=n;i++)
       inv[i]=(ll)p-(p/i)*inv[p%i]%p,printf("%d\n",inv[i]);
   return 0;
}

这样再提交就AC了

这种做法复杂度较高但是可以批量求逆元,在这类题目中非常好用。

7.中国剩余定理(CRT)

P1495 【模板】中国剩余定理(CRT)/曹冲养猪

直接看题

中国剩余定理又称孙子定理~~(所以曹冲和猪为什么被迫害了)~~

题意就是给了一堆同余方程,让求x的值:

最佳阅读效果(这里引用了OI Wiki的证明)

算法流程

代码:

// C++ Version
LL CRT(int k, LL* a, LL* r) {
  LL n = 1, ans = 0;
  for (int i = 1; i <= k; i++) n = n * r[i];
  for (int i = 1; i <= k; i++) {
    LL m = n / r[i], b, y;
    exgcd(m, r[i], b, y);  // b * m mod r[i] = 1
    ans = (ans + a[i] * m * b % mod) % mod;
  }
  return (ans % mod + mod) % mod;
}

算法证明:

你们可能发现了,yzh引用了大量OI Wiki上的文章,因为OI Wiki写的实在是太好了~~(其实就是我懒的写了)~~,中国剩余定理也不难,所以我并不打算重点写
若属于侵权行为,OI Wiki工作人员可联系我删除

最近更新:2022/1/7,未完

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值