详解数论从入门到入土

本蒟蒻第一次讲课,由于比较匆忙所以没有及时准备课件,在此表示抱歉…
听说他们讲课都是啥都没有 在那儿唠嗑 20 20 20分钟然后问:有没有不会的 不会的自己学 就完事了?? 2333 2333 2333真是负责

前景提要

这是一篇数论 0 0 0零起点到负无穷,从入门到入土的博客 请大家做好心理准备

听说 C C C层要讲数论?
哈 作为前数学竞赛生的我当然要抢着讲了 于是跟 p j t pjt pjt大哥蹭了一下
本着放松随便讲一讲的心态 我问了我们这边的 l y s s lyss lyss小宝宝
在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述

但是 p j t pjt pjt去自由啊…那就我自己写吧…

t i p s : tips: tips:现在要讲的基本涉及的都是整数 所用的字母除声明外也都表示整数

P a r t 1. Part1. Part1.整数的常识

一、整除

1.设 a a a b b b是给定的数 , b ≠ 0 ,b\neq0 b=0。若存在整数 c , c, c使得 a = b c , a=bc, a=bc则称 b b b整除 a , a, a记作 b ∣ a , b\mid a, ba反之 , , ,则称 b b b不能整除 a , a, a记作 b ∤ a b\nmid a ba
2.一些性质:

a. 若 a ∣ b a|b ab,且 b ∣ c b|c bc,则 a ∣ c a|c ac
b. 若 b ∣ a b|a ba,且 b ∣ c b|c bc,则 b ∣ ( a ± c ) b|(a\pm c) b(a±c)
c.若 c ∣ a c|a ca,且 c ∣ b c|b cb,则对于任意整数m、n,有 c ∣ ( m a + n b ) c|(ma+nb) c(ma+nb)

二、素数与合数

         \,\,\,\,\,\,\,\, 对于一个正整数数,如果它有且仅有1和它自己两个约数,那么我们称这个数为素数。如果有两个以上的约数,那么我们称这个数为合数。注意:1既不是素数也不是合数

先植入一个没什么用的定理,它叫素数定理:
小于x的素数的个数近似等于x/ln(x)…

现在我们想想如何求素数

1.考虑暴力枚举

如果 2 − n 2 -\sqrt{n} 2n 每个数都不是 n n n的因子,那么 n n n就是质数了。
复杂度 O ( n ) O(\sqrt{n}) O(n )

那么我们来看一道题:
1 − n 1-n 1n内的素数。 n < = 1000000 n<=1000000 n<=1000000

用上述的算法那是要跑 100 s 100s 100s的 所以我们就需要换一个高效的算法

2.筛法求素数
基本思路是:素数的倍数不是素数
         \,\,\,\,\,\,\,\, 1不是素数首先把它筛掉,剩下的数中最小的数一定是素数,然后去掉它的倍数。
v i s [ i ] vis[i] vis[i]表示 i i i是否被访问过 , c n t ,cnt ,cnt表示现在素数的数量 , p r i m e ,prime ,prime数组存的是从小到大的素数
代码:

for(int i=2;i<=n;i++){
    
    
	if(!vis[i]){
    
    
		prime[++cnt]=i;
		for(int j=1;j*i<=n;j++)vis[i*j]=true;
	}
}

这回由 O ( n n ) O(n\sqrt{n}) O(nn )变成了 O ( n   l o g n ) O(n\,log_n) O(nlogn)
但是我们注意到,这样筛会筛重很多次。
我们拿 75 75 75举例 :在筛到素数 3 3 3时我们把它筛除 在筛到素数 5 5 5时我们又会筛除一次 这样会浪费大量的时间,如何优化呢?
3.线性筛
基本思路是:在筛法的基础上 我们让每一个合数只能被他自己最小的素数筛到
如何实现这个优化呢?
看代码

for(int i=2;i<=n;i++){
    
    
	if(!vis[i])prime[++cnt]=i;
	for(int j=1;j<=cnt&&i*prime[j]<=n;j++){
    
    
		vis[i*prime[j]]=true;
		if(i%prime[j]==0)break;
	}
}

在筛到每个数时 我们把小于它的最小质数的所有质数倍数的数都筛掉 这样就能保证每个数是被它自己最小的质因子筛掉

P a r t 2. Part2. Part2. g c d & l c m gcd\&lcm gcd&lcm

g c d gcd gcd是啥? l c m lcm lcm是啥?某党?

g c d gcd gcd指的是 g r e a t e s t    c o m m o n    d i v i s o r greatest\,\,common\,\, divisor greatestcommondivisor就是最大公约数。
l c m lcm lcm指的是 L e a s t    C o m m o n    M u l t i p l e , Least\,\,Common\,\,Multiple, LeastCommonMultiple即最小公倍数。

一、最大公约数

最大公约数是数论中一个重要的概念

a a a b b b不全为零 , , ,同时整除 a a a b b b的整数称为他们的公约数 , , 显然 a a a b b b的公约数只有有限多个 , , 我们将其中最大的一个称为 a a a b b b的最大公约数表示 , , 用符号 ( a , b ) (a,b) (a,b)表示。显然 , , 最大公约数是一个正整数。
( a , b ) = 1 (a,b)=1 (a,b)=1 , , 我们称 a a a b b b互质 ( ( (互素 ) , ), )这种情形特别重要。

那么问题来了 我们怎么求最大公约数呢?
通常用辗转相除法来写 我的个人喜好是用递归
辗转相除法是不都会…? 算了算了 好好讲讲吧
我们可以很显然地理解这个等式:

g c d ( a , b ) = g c d ( a − b , b ) gcd(a,b)=gcd(a-b,b) gcd(a,b)=gcd(ab,b)

但是呢 这么一次一次减太慢了 所以我们一次能减多少就减多少
就相当于直接除 这就是简述版的辗转相除

int gcd(int a, int b){
    
    
	if(b==0)return a;
	else return gcd(b,a%b);

我不会告诉你们algorithm库里有可以直接用的__gcd
辗转相除在后面个的扩展 g c d gcd gcd中还是很有用的
上两个题吧

luoguP1372
简述版题意:给你个 n n n k k k n n n个数中取 k k k个的最大公约数最大

S o l u t i o n : Solution: Solution:设这 k k k个数的最大公约数为 g c d gcd gcd
则第 k k k个数最小为 k × g c d k×gcd k×gcd 所以 k × g c d ≤ n k×gcd\leq n k×gcdn
那么 g c d ≤ n k gcd\leq\frac{n}{k} gcdkn 显然最大的 g c d = ⌊ n k ⌋ gcd=\lfloor\frac{n}{k}\rfloor gcd=kn

#include<iostream>
using namespace std;
int main()
{
    
    
    int n,k;
    cin>>n>>k;
    cout<<n/k<<endl;
}

luoguP2090
对于一个数字对(a, b),我们可以通过一次操作将其变为新数字对(a+b, b)或(a, a+b)。

给定一正整数n,问最少需要多少次操作可将数字对(1, 1)变为一个数字对,该数字对至少有一个数字为n。
S o l u t i o n : Solution: Solution:
注意到等式 g c d ( a , b ) = g c d ( a , a + b ) gcd(a,b)=gcd(a,a+b) gcd(a,b)=gcd(a,a+b)
所以找到 1 1 1 n − 1 n-1 n1 g c d ( a , b ) gcd(a,b) gcd(a,b)递归层数最少的就好咯
在这儿推一下那天的模拟赛的解题报告 其中就有这道题 模拟赛不是很难大家可以看看
最后有彩蛋哦
地址在这!:题解

#include<bits/stdc++.h>
using namespace std;
#define inf int(1e8)
inline void read(int &x){
    
    
    int s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){
    
    if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){
    
    s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
    x=s*w;
}
int gcdd(int a, int b){
    
    
    if(!b)return inf;
    if(b==1)return a-1;
    return gcdd(b,a%b)+a/b;
}
int n,now=inf;
int main(){
    
    
    read(n);
    for(int i=1;i<=(n+1)/2;i++)now=min(now,gcdd(n,i));
    printf("%d\n",now);
}

二、最小公倍数

那么我们再简单地谈谈最小公倍数
a 、 b a、b ab是两个非零正整数 , , 一个同时为 a 、 b a、b ab的倍数的书称为他们的一个公倍数 , , ,两个数的公倍数显然有无穷多个 , , ,我们把这其中的最小的正整数称为他们的最小公倍数

那最小公倍数就很好求了

l c m ( a , b ) = a b g c d ( a , b ) lcm(a,b)=\frac{ab}{gcd(a,b)} lcm(a,b)=gcd(a,b)ab

U P D : UPD: UPD:昨天讲到这里哦!
经过一

P a r t 3. Part3. Part3.同余及应用

一、同余

同余是数论中的一个重要概念,应用极为广泛。
n n n是给定的正整数,若整数 a 、 b a、b ab满足 n ∣ ( a − b ) , n\mid(a-b), n(ab)则称 a a a b b b n n n同余 , , ,记作

a ≡ b ( m o d    n ) a\equiv b(mod\,\,n) ab(modn)

反之,则称 a a a b b b n n n不同余 , , ,记作

a ≢ b ( m o d    n ) a\not\equiv b(mod\,\,n) ab(modn)

很显然, a a a b b b n n n同余的充分必要条件是 a a a b b b n n n除得的余数相同。

同余式的几个性质:

  1. 反身性: a ≡ a ( m o d    n ) a\equiv a(mod\,\,n) aa(modn)
  2. 对称性:若 a ≡ b ( m o d    n ) , a\equiv b(mod\,\,n), ab(modn) b ≡ a ( m o d    n ) b\equiv a(mod\,\,n) ba(modn)
  3. 传递性:若 a ≡ b ( m o d    n ) , a\equiv b(mod\,\,n), ab(modn) b ≡ c ( m o d    n ) , b\equiv c(mod\,\,n), bc(modn) a ≡ c ( m o d    n ) a\equiv c(mod\,\,n) ac(modn)
  4. 可加性:若 a ≡ b ( m o d    n ) , a\equiv b(mod\,\,n), ab(modn) c ≡ d ( m o d    n ) , c\equiv d(mod\,\,n), cd(modn) a ± c ≡ b ± d ( m o d    n ) a\pm c\equiv b\pm d(mod\,\,n) a±cb±d(modn)
  5. a ≡ b ( m o d    n ) , a\equiv b(mod\,\,n), ab(modn) c ≡ d ( m o d    n ) , c\equiv d(mod\,\,n), cd(modn) a c ≡ b d ( m o d    n ) ac\equiv bd(mod\,\,n) acbd(modn)
    运用这几个性质 我们把同余应用在信息中的数论

二、快速幂

给定 a 、 n 、 m a、n、m anm a n    m o d    k a^n\,\,mod\,\,k anmodk
考虑朴素算法 用循环一个一个累乘 可以在 O ( n ) O(n) O(n)的时间内求出答案
不过似乎没什么用 这一点都不快速
那么我们来点快的
考虑到 a 2 n = a n 2 a^{2n}={a^n}^{2} a2n=an2所以我们可以两两配对地乘
所以
1. 1. 1.当n是奇数时,那么有 a n = a ∗ a n − 1 a^{n} = a * a^{n-1} an=aan1

2. 2. 2.当n是偶数时,那么有 a n = a n 2 ∗ a n 2 a^n = a^\frac{n}{2}* a^\frac{n}{2} an=a2na2n
上代码

int quickpow(int a, int n, int m){
    
    
	int ret=1;
	while(b){
    
    
		if(b&1)(ret*=a)%=m;
		(a*=a)%=m,n>>=1;
	}
}

时间复杂度: O ( l o g n ) O(log_n) O(logn)
10.23     23 : 12 10.23\,\,\,23:12 10.2323:12今天先写到这儿 明天应该讲不到这儿

三、扩展欧几里得

引题:给定 a 、 b 、 c , a、b、c, abc求使得 a x + b y = c ax+by= c ax+by=c成立的最小正整数解 x 、 y , x、y, xy如果无解则输出 l y s w a n lyswan lyswan
讲真的 我是从这个题开始认识到信息真的是一门竞赛…

S o l u t i o n : Solution: Solution:
1. 1. 1.首先考虑是否有解:当 g c d ( a , b ) ∣ c gcd(a,b)\mid c gcd(a,b)c时方程才有解
2. 2. 2.所以我们先不管 c c c到底是 g c d ( a , b ) gcd(a,b) gcd(a,b)的几倍 直接给
我们考虑如何搞这个方程:
首先看这个方程: b x + ( a % b ) y = c bx+(a\%b)y= c bx+(a%b)y=c这样以此类推
最后会得到 g c d ( a , b ) x = c gcd(a,b)x= c gcd(a,b)x=c此时 x = 1 x=1 x=1
对于 a ′ = b , b ′ = a % b a' = b, b' = a\% b a=b,b=a%b而言,我们求得 x , y x, y x,y使得 a ′ x + b ′ y = g c d ( a ′ , b ′ ) , a'x + b'y = gcd(a', b'), ax+by=gcd(a,b) 由于 b ′ = a % b = a − ⌊ a b ⌋ × b b' = a \% b = a - \lfloor\frac{a}{b}\rfloor ×b b=a%b=aba×b
所以可以推得 a y + b ( x − ⌊ a b ⌋ × y ) = g c d ( a , b ) ay +b(x - \lfloor\frac{a}{b}\rfloor ×y) = gcd(a, b) ay+b(xba×y)=gcd(a,b)
即一组通解为 x = y , y = x − ⌊ a b ⌋ × y x=y,y=x - \lfloor\frac{a}{b}\rfloor ×y x=y,y=xba×y

问题来了 我们怎么用代码实现呢?
注意到我们方程的变形用的是 g c d gcd gcd函数辗转相除,方程的通解是一步一步递归才能得到 所以用递归版的欧几里得顺便求得。
这个就叫扩展欧几里得 上代码:

void extended_gcd(int a, int b, int &x, int &y){
    
    
    if(b==0){
    
    x=1,y=0;return;}
    exgcd(b,a%b,x,y);
    int t=y;
    y=x-(a/b)*y,x=t;
}

最后的 x , y x,y x,y就是解 不过出来可能会是一个负数 求最小正整数解还要再加上一个 m o d mod mod

这个东西其实真的很有用的 我们看一道题
N O I P 2012 NOIP2012 NOIP2012同余方程
题意:求关于 x x x的同余方程 a x ≡ 1 ( m o d b ) a x \equiv 1 \pmod {b} ax1(modb)的最小正整数解。

S o l u t i o n : Solution: Solution:转化为 a x + b y = 1 ax+by=1 ax+by=1 就变成了扩展欧几里得…
代码:

#include<cstdio>
int t,c,d,x,y;
void exgcd(int a, int b, int &x, int &y)
{
    
    
    if(a%b==0)
    {
    
    
        x=0;
        y=1;
        return;
    }
    exgcd(b,a%b,x,y);
    t=x;
    x=y;
    y=t-a/b*y;
}
int main()
{
    
    
    scanf("%d%d",&c,&d);
    exgcd(c,d,x,y);
    while(x<=0)
    {
    
    
        x+=d;
    }
    printf("%d\n",x);
}

四、欧拉函数

欧拉函数,即 φ ( x ) φ(x) φ(x),简单说它表示从 1 − n 1-n 1n中和它互质的数的个数
欧拉函数在各种玄学定理中经常出现,有的题中 g c d = 1 gcd=1 gcd=1 的情形往往可以转化为欧拉函数
如何求欧拉函数
1. 1. 1.单个数欧拉函数的求解公式:
对于一个数 x = ∏ i = 1 n p i α i , x=\prod_{i=1}^n p_i^{\alpha_i}, x=i=1npiαi那么 φ ( x ) = x ∏ i = 1 n ( 1 − 1 p i ) φ(x)=x\prod_{i=1}^n (1-\frac{1}{p_i}) φ(x)=xi=1n(1pi1)
至于为啥有兴趣的自己推 不难推,这里就不赘述了。主要是…我现在要码不完了啊啊估计明天也讲不完了所以直接过吧…求饶 . j p g .jpg .jpg
2. 2. 2. 线性求 1 − n 1-n 1n 所有数的欧拉函数 ( ( (即欧拉筛 ) ) )
上述求解欧拉函数不是没有用 实在是太磨叽…又要分解质因数,又要求乘积,还有分数…没看我代码都没给吗2333 但有时候这个式子推一些性质很有用的
我们欧拉筛之前先要了解几个性质:
a . a. a.对于质数 p p p,有 φ ( p ) = p − 1 φ(p)=p-1 φ(p)=p1 显然…
b . b. b. ( n , m ) = 1 (n,m)=1 (n,m)=1时,有 φ ( n × m ) = φ ( n ) × φ ( m ) ( φ(n×m)=φ(n)×φ(m)( φ(n×m)=φ(n)×φ(m)(即欧拉函数是但不完全是积性函数 ) ) ),特殊地, φ ( 2 n ) = φ ( n ) φ(2n)=φ(n) φ(2n)=φ(n)
用上面的定义式这个显然成立
c . c. c. m ∣ n m\mid n mn时, φ ( m × n ) = m × φ ( n ) φ(m×n)=m×φ(n) φ(m×n)=m×φ(n)
用定义式回去自己推不会问我…
那么这些性质我们了解之后,就可以筛筛筛筛筛筛了
首先想一想 上面的性质是不是有点眼熟? p p p 为质数, n % m = 0 n\%m=0 n%m=0
没错就是线性筛,我们可以在线性筛素数的时候,运用这三条性质来维护欧拉函数,具体看代码 我要讲不完了!!

euc[1]=1;
for(int i=2;i<=n;i++)
{
    
    
    if(!vis[i])euc[i]=i-1,prime[++cnt]=i;
    for(int j=1;j<=cnt;j++)
    {
    
    
        if(i*prime[j]>n)break;
        vis[i*prime[j]]=true;
        if(i%prime[j]==0)
        {
    
    
            euc[i*prime[j]]=euc[i]*prime[j];
            break;
        }
        else euc[i*prime[j]]=euc[i]*euc[prime[j]];
    }
}

仔细体会其实不难
上例题:
题面找不到了 大哥们我错了
题意:给定 n n n,求 1 ≤ x , y ≤ n 1\leq x,y\leq n 1x,yn g c d ( x , y ) = 1 gcd(x,y)=1 gcd(x,y)=1的数对个数
这就用到我们前面讲的 某些问题的 g c d = 1 gcd=1 gcd=1可以转化为欧拉函数
用式子写出来就是:
∑ i = 1 n ∑ j = 1 n ( g c d ( i , j ) = 1 ) \sum_{i=1}^n \sum_{j=1}^n (gcd(i,j)=1) i=1nj=1n(gcd(i,j)=1)
= ∑ i = 1 n φ ( i ) =\sum_{i=1}^nφ(i) =i=1nφ(i)
所以只需要求出欧拉函数的前缀和即可

五、逆元

终于来到最有用的地方了…

什么是逆元?
对于一个数 x , x, x它在模 p p p意义下的逆元 a a a,满足 a x ≡ 1 ( m o d p ) , ax\equiv 1\pmod p, ax1(modp)
这东西很有用 可以说没有逆元数论少了灵魂
看这个题:给定 n , m , p n,m,p n,m,p n m m o d    p \frac{n}{m}mod\,\,p mnmodp
如果你没学逆元 你肯定懵逼: w o c ? ? woc?? woc??分数还能取模?
这就是逆元的妙处
S o l u t i o n : Solution: Solution:求出 m m m在模 p p p意义下的逆元 a a a,答案为 n a % p na\%p na%p
那么这么好用的一个东西 我们怎么求呢???
求逆元的方法

1. 1. 1. e x g c d exgcd exgcd
逆元满足什么条件? a x ≡ 1 ( m o d p ) ax\equiv 1\pmod p ax1(modp)?同余方程啊! e x g c d exgcd exgcd显然可行啊。
性能分析:

  • 时间复杂度 O ( l o g m a x ( a , b ) ) O(log_{max(a,b)}) O(logmax(a,b)) 不是太差
  • 适用范围:只要存在逆元就可求,适用个数不多,当 m o d mod mod很大很大时是个非常不错唯一的选择
  • 缺点:递归~讨厌厌…这是最常见以及我最不愿意打的一个…

2. 2. 2.快速幂
快速幂怎么求逆元?
那首先你需要了解一个定理—费马小定理:

p p p为素数,那么 a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1\pmod p ap11(modp)

这个是怎么来的呢? 欧拉定理的推论
欧拉定理(有时也叫作费马小定理的一般式):

a φ ( p ) ≡ 1 ( m o d p ) a^{φ(p)}\equiv 1\pmod p aφ(p)1(modp)

那欧拉定理怎么来的呢?有兴趣自己查查(其实是来不及了!!
接着说 因为 a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1\pmod p ap11(modp),所以 a p − 2 a^{p-2} ap2就是 a a a的逆元
性能分析:

  • 时间复杂度: O ( l o g m o d ) O(log_{mod}) O(logmod)
  • 适用范围:在 m o d mod mod素数!! 并且 m o d mod mod不是太太太大大大的时候
  • 优点:比扩欧快而且好写,我最喜欢求逆元的方法之一
  • 缺点:如果 m o d mod mod是合数相信没有人无聊到筛一遍欧拉函数

3. 3. 3. 线性求逆元
放心放心 这次线性没有筛 不用害怕
原理: p p p是模数,我们现在要求的是 i i i的逆元
p p p写成 p = k ∗ i + r p=k∗i+r p=ki+r其中 0 < r < i 0<r<i 0<r<i(就是带余除法)
k = p i , r = p % i k=\frac{p}{i},r=p\%i k=ip,r=p%i
在这里插入图片描述
解释一下 第二行是第一行两边都除 i r ir ir得到的
总的来说就是一个公式:
i n v [ i ] = − ( m o d / i ) ∗ i n v [ i % m o d ] inv[i]=-(mod/i)*inv[i\%mod] inv[i]=(mod/i)inv[i%mod]边界是 i n v [ 1 ] = 1 inv[1]=1 inv[1]=1
优点:
a . a . a. O ( n ) O(n) O(n)的时间内求出 1 − n 1-n 1n 的所有逆元 高效
b . b. b.代码好打 就一行
c . c. c.没有缺点
代码:

inv[1]=1;
for(int i=2;i<mod;i++)inv[i]=(mod-mod/i)*inv[i%mod];

回到前面的问题,现在我们会求逆元了,分数取模自然也就很水了
有理数取余
题意:给出一个有理数 c = a b c=\frac{a}{b} c=ba,求 c     m o d   19260817 c\ \bmod 19260817 c mod19260817c的值。
在这里插入图片描述
S o l u t i o n : Solution: Solution:直接求 b b b的逆元,由于19260817是个质数,所以输出 a × b m o d − 2 a×b^{mod-2}%mod a×bmod2就好了,由于 a , b a,b a,b很大 所以需要处理一下:↓

#include<cstdio>
typedef long long ll;
const ll mod=19260817;
ll quickpow(ll a, ll b){
    
    
    ll ret=1;
    while(b){
    
    
        if(b&1)(ret*=a)%=mod;
        (a*=a)%=mod,b>>=1;
    }
    return ret;
}
ll n,m;
ll getint(){
    
    
    char chr=getchar();ll x=0;
    while(chr<'0'||chr>'9')chr=getchar();
    while(chr>='0'&&chr<='9'){
    
    
        x=(x*10)%mod;
        chr=getchar();
    }
    return x;
}
int main(){
    
    
    n=getint(),m=getint();
    if(m==0)return printf("Angry!"),0;
    printf("%lld\n",n*quickpow(m,mod-2)%mod);
}

板子题
题意:给定 n , p n,p n,p 1 − n 1-n 1n中所有整数在模 p p p意义下的乘法逆元。
S o l u t i o n : Solution: Solution:没有 S o l u t i o n Solution Solution,线性求逆元直接过

#include<bits/stdc++.h>
#define N 3000010
typedef long long ll;
using namespace std;
int inv[N],n,p;
inline int read(){
    
    
    int f=1,x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
    
    if(ch=='-')f=-1;ch=getchar();};
    while(ch>='0'&&ch<='9'){
    
    x=(x<<3)+(x<<1)+(ch&15);ch=getchar();};
    return f*x;
}
int main(){
    
    
    n=read();p=read();inv[1]=1;puts("1");
    for(int i=2;i<=n;i++){
    
    
        inv[i]=(ll)(p-p/i)*inv[p%i]%p;
        printf("%d\n",inv[i]);
    }
}

逆元是一种非常有用的工具 它可以和很多有用的定理结合在一起形成扩展定理

中国剩余定理

中国剩余定理,也称孙子定理,话说昨天你们为什么对这个定理这么感兴趣呢…?
中国剩余定理能干啥呢?
比如 一筐苹果 三个三个吃最后剩两个,四个四个吃最后剩三个,五个五个吃最后剩四个,求一共有多少个苹果?
答案是 59 59 59个.
当然中国剩余定理不只这么简单
给定 k k k以及 k k k a i 、 m i a_i、m_i aimi求一个最小的正整数 n n n满足
{ n ≡ a 1 ( m o d    m 1 ) n ≡ a 2 ( m o d    m 2 ) . . . n ≡ a k ( m o d    m k )   \begin{cases} n\equiv a_1(\mod m_1)\quad \\ n\equiv a_2(\mod m_2)\quad \\... \\ n\equiv a_k(\mod m_k)\quad \ \end{cases} na1(modm1)na2(modm2)...nak(modmk) 
其中 ( m 1 , m 2 , . . . , m k ) = 1 (m_1,m_2,...,m_k)=1 (m1,m2,...,mk)=1
如何求解呢?
M = ∏ i = 1 k m i M=\prod_{i=1}^km_i M=i=1kmi,即 M M M是所有 m i m_i mi 的最小公倍数
对于每个方程,设 p i = M m i p_i=\frac{M}{m_i} pi=miM
t i t_i ti为同余方程 p i t i ≡ 1 ( m o d    m i ) p_it_i\equiv 1(mod\,\,m_i) piti1(modmi)的最小非负整数解
则有一个解 x = ∑ i = 1 k a i M m i t i x=\sum_{i=1}^ka_i\frac{M}{m_i}t_i x=i=1kaimiMti
​通解为 x + i ∗ M ( i ∈ Z ) x+i∗M(i∈Z) x+iM(iZ)
特别地,最小非负整数解为 ( x % M + M ) % M (x\%M+M)\%M (x%M+M)%M
自己推推 很好理解 盲猜时间应该不够了 所以不讲了 不会问我
代码实现:求 t i t_i ti那个同余方程时要用到扩展欧几里得
其他的…入门难度的代码实现

void ex_gcd(ll n, ll m, ll &x, ll &y){
    
    
    if(!m){
    
    x=1,y=0;return;}
    ex_gcd(m,n%m,y,x);y-=(n/m)*x;
}
ll CRT(){
    
    
    ll ans=0,lcm=1,x,y;
    for(int i=1;i<=k;i++)lcm*=b[i];
    for(int i=1;i<=k;i++){
    
    
        t[i]=lcm/b[i];
        ex_gcd(t[i],b[i],x,y);
        x=(x%b[i]+b[i])%b[i];
        ans=(ans+t[i]*a[i]%lcm*x%lcm)%lcm;
    }
    return (ans+lcm)%lcm;
}

例题例题例题:
T J O I 2009 TJOI2009 TJOI2009猜数字
简述题意:给个 t t t t t t组数据,每组数据给 k k k以及 k k k a i 、 m i a_i、m_i aimi求最小的非负整数 n n n,满足对于任意的 i , n − a i i,n - a_i inai能被 b i b_i bi整除。

S o l u t i o n : Solution: Solution:板子题板子题板子题!!!
从这个:
在这里插入图片描述
推到这个:
在这里插入图片描述
即:

在这里插入图片描述
题中给的 ( b 1 , b 2 , . . . , b k ) = 1 (b_1,b_2,...,b_k)=1 (b1,b2,...,bk)=1所以中国剩余定理可做。
注意:
1. 1. 1.直接乘爆 l o n g l o n g long long longlong要用快速乘,快速乘其实是慢速乘…具体跟快速幂差不多,看代码
2. 2. 2.直接快速乘会 T L E TLE TLE因为快速乘不能有负数,而题中说数可能为负数,所以如果是负数要加个 m o d mod mod
这两个地方当时差点没坑死我…
代码↓

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll quickpow(ll x, ll y, ll mod){
    
    
    ll ret=1;
    while(y){
    
    
        if(y&1)(ret*=x)%=mod;
        (x*=x)%=mod,y>>=1;
    }
    return ret;
}
inline ll quickmul(ll x, ll y, ll mod){
    
    
    if(x<0)x+=mod;if(y<0)y+=mod;
    if(y>x)swap(x,y);
    ll ret=0;
    while(y){
    
    
        if(y&1)(ret+=x)%=mod;
        (x+=x)%=mod,y>>=1;
    }
    return ret;
}
ll k,a[20],b[20],t[20];
void ex_gcd(ll n, ll m, ll &x, ll &y){
    
    
    if(!m){
    
    x=1,y=0;return;}
    ex_gcd(m,n%m,y,x);y-=(n/m)*x;
}
ll CRT(){
    
    
    ll ans=0,lcm=1,x,y;
    for(int i=1;i<=k;i++)lcm*=b[i];
    for(int i=1;i<=k;i++){
    
    
        t[i]=lcm/b[i];
        ex_gcd(t[i],b[i],x,y);
        x=(x%b[i]+b[i])%b[i];
        ans=(ans+quickmul(quickmul(t[i],a[i],lcm),x,lcm))%lcm;
    }
    return (ans+lcm)%lcm;
}
int main(){
    
    
    scanf("%lld",&k);
    for(int i=1;i<=k;i++)scanf("%lld",&a[i]);
    for(int i=1;i<=k;i++)scanf("%lld",&b[i]);
    printf("%lld\n",CRT());
}

此外,当模数不是互质时,我们就会用到扩展中国剩余定理了,有兴趣的可以自己学一下

P a r t 4. Part4. Part4.组合数学

组合数学是信息中的数论的一个重要分支
不过讲不完了
加我 Q Q : 407694747 QQ:407694747 QQ:407694747免费一对一辅导,不容错过…

猜你喜欢

转载自blog.csdn.net/dhdhdhx/article/details/102690006
今日推荐