数论基础

数论是纯数学的一个研究分支,主要研究整数的性质。初等数论包括整除理论、同余理论、连分数理论。这一篇主要记录的是同余相关的基础知识。

取模

取模是一种运算,本质就是带余除法,运算结果就是余数。取模运算结果的符号由被模数(被除数)决定。

\[7\%4=3;\space7\%(-4)=3;\\
(-7)\%4=-3;\space(-7)\%(-4)=-3
\]

取模运算的性质

\[设a>b>0,有:\\
(a+b)\%c=(a\%c+b\%c)\%c\\
(a-b)\%c=(a\%c-b\%c+c)\%c \\
(a\times b)\%c=(a\%c\times b\%c)\%c
\]

例题1

\[给定两个非负整数a,b和正整数n,计算f(a^b)\%n。\\
其中,f(0)=f(1)=1,且对于\forall i>0,f(i+2)=f(i+1)+f(i)\\
(0\le a,b\le 2^{64},1\le n\le 1000)
\]

找规律,对于mod n来说,最多n²项就会出现重复,找出重复项即可得到循环周期,然后找对应的项即可。

例题2:hdu 1212

题意是给一个位数不超过1,000的正整数A和一个大小不超过100,000的正整数B,求A%B。

这里只需要用到上面的两条性质,把A截断,从高到低模拟做除法即可。

#include <iostream>
string s; int mod, ans;
int main() {
while(std::cin >> str >> mod) {
ans = 0;
for(int i = 0; s[i]; i++) ans = (ans * 10 + (s[i] - '0')) % mod;
std::cout << ans << std::endl;
}
return 0;
}

GCD

GCD即Greatest Common Divisor,最大公约数。最大公约数即能同时整除给定两个整数的最大正整数。特殊地:gcd(a,0)=a。求解最大公约数通常使用辗转相除法。下面是辗转相除法的代码实现:

typedef long long LL;
LL gcd(LL a, LL b) {
return b ? gcd(b, a % b) : a;
}

辗转相除法得到的余数序列增长速度比斐波那契数列更快,已知斐波那契的增长是指数级别,则辗转相除法的复杂度是对数级别。

辗转相除法的证明:

1.设两个数a、b(a>b),他们的最大公约数为gcd(a,b),r = a % b,k = (a - a % b)/ b,那么证明辗转相除法,即是要证明:gcd(a,b)=gcd(b,r)。首先我们令c = gcd(a,b),那么肯定存在互质的整数m,n,使得a = mc,b = nc。那么有r = a - kb = mc - knc = (m - kn)c,根据这条式子,c也是r的因数。回过头看,如果能证明m-kn和n互质,那么就可以说明c=gcd((m-kn)c,nc)=gcd(b,r),所以把问题再次转化为:求证m-kn和n互质。

2.反证法证明m-kn和n互质:假设m-kn和n不互质,用数学语言描述为:假设存在整数x,y,d,其中d>1,使得m-kn=xd,n=yd。那么有m=kn+xd=kyd+xd=(ky+x)d,从而a=mc=(ky+x)cd,b=nc=ycd,则gcd(a,b)=cd≠c,与前设矛盾。故m-kn和n互质得证,也就证明了gcd(a,b)=gcd(b,r)。

唯一分解定理

对于任意大于2的正整数X,它总能写成如下形式:

\[X=p_1^{a_1}\times p_2^{a_2} \times ......\times p_k^{a_k},其中p是各不相同的质数
\]

即质因数分解。且这个分解唯一。

LCM

LCM即Least Common Multiple,最小公倍数。

\[\begin{align}
&根据唯一分解定理,对于给定的a,b:\\
&a=p_1^{a_1}\times p_2^{a_2} \times......\times p_k^{a_k}\\
&b=p_1^{b_1}\times p_2^{b_2} \times......\times p_k^{b_k}\\
&那么:\\
&gcd(a,b)=p_1^{min(a_1,b_1)}\times p_2^{min(a_2,b_2)}\times ......p_1^{min(a_k,b_k)}\\
&lcm(a,b)=p_1^{max(a_1,b_1)}\times p_2^{max(a_2,b_2)}\times ......p_1^{max(a_k,b_k)}\\
&因此,gcd(a,b)\times lcm(a,b)=a\times b
\end{align}
\]

LL lcm(LL a, LL b) {
return a / gcd(a, b) * b;
}

例题:hdu1788

题意是求最小的正整数N,满足除以每一个给定的数Mi,其结果均为Mi-a。作如下转化:

\[N\%M_i\equiv(M_i-a)\%M_i\Rightarrow (N+a)\%M_i=0(a<M_i<100)
\]

即N是Mi的倍数,那么题意也就是求所有M的最小公倍数,注意数字范围即可。

同余

对于两个不同的数a,b,如果有a % p = b % p(p>1),那我们就称a和b模p同余,记作:

\[a\equiv b(mod\space p)
\]

同余的性质

\[\begin{aligned}
&1.自反性:a\equiv a(mod\space m)\\
&2.对称性:a\equiv b(mod\space m),则b\equiv a(mod\space m)\\
&3.传递性:若a\equiv b(mod\space m),b\equiv c(mod\space m),则a\equiv c(mod\space m)\\
&4.线性运算:若a\equiv b(mod\space m),c\equiv d(mod\space m)\\
&\space\space\space则:a\pm c\equiv b\pm d(mod\space m);\space a\times c\equiv b\times d(mod\space m)\\
&5.幂运算:若a\equiv b(mod\space m),那么a^n\equiv b^n(mod\space m)\\
&6.若ac\equiv bc(mod\space m),且c≠0,那么a\equiv b(mod\space {m\over gcd(c,m)})\\
&\space\space\space特殊地,若gcd(c,m)=1,则a\equiv b(mod\space m)
\end{aligned}
\]

EXGCD

贝祖定理(Bezouts Identity):若设a,b是不全为0的整数,则存在整数x,y,使得ax+by=gcd(a,b),(a,b)代表最大公因数,则设a,b是不全为零的整数,则肯定存在整数x,y,使得ax+by=(a,b)。这个式子称为贝祖等式。

EXtend GCD 即扩展欧几里得算法,它可以用于解关于x,y的贝祖等式。

EXGCD的可行性

当a=0时,ax+by=gcd(a,b)=gcd(0,b)=b,这时可以解出y=1而x为任意。同理可得b=0时,解得x=1而y为任意。当a,b都不为0时:

\[\begin{aligned}
&设存在一组解为x_1,y_1,即ax_1+by_1=gcd(a,b)\\
&由欧几里得定理:gcd(a,b)=gcd(b,a\%b)得:\\
&ax_1+by_1=gcd(a,b)=gcd(b,a\%b)\\
&那么,bx_2+(a\%b)y_2=gcd(b,a\%b)=ax_1+by_1\\
&又a\%b=a-a/b\times b\\
&\begin {align}
那么,&ax_1+by_1\\
=&bx_2+(a-a/b\times b)y_2\\
=&bx_2+ay_2-a/b*b*y_2\\
=&ay_2+b(x_2-a/b*y_2)
\end{align}\\
&故可以得到:x_1=y_2,\space y_1=x_2-a/b*y_2
\end{aligned}
\]

a或b为0即迭代求解的出口,迭代过程用代码表示如下:

typedef long long LL;
// ver 1
// 返回值为gcd(a,b),x和y即求出的特解
LL exgcd(LL a, LL b, LL &x, LL &y) {
if (b == 0) {
x = 1; y = 0;
return a;
}
LL ans = exgcd(b, a % b, x, y);
//这里通过迭代求出了一组解x,y,这组解对应上面推导过程中的x2和y2。
LL temp = x;
x = y;
y = temp - a / b * y;
return ans;
} // ver 2
// 在熟悉了推导过程之后,我们可以利用引用的性质得到更为简洁的ver2写法
LL exgcd(LL a, LL b, LL &x, LL &y) {
if (b == 0) {
x = 1; y = 0;
return a;
}
LL ans = exgcd(b, a % b, y, x);
y -= a / b * x;
return ans;
}

EXGCD的应用

求关于x,y的方程ax+by=c的一组解

这里只需要对贝祖等式稍作变换即可。假设EXGCD求出的方程ax+by=gcd(a,b)的一组解为x1和y1。在方程两边同时乘上一个数m,使得c=m*gcd(a,b),这里也就要求c是gcd(a,b)的倍数,即c%gcd(a,b)=0。这也是方程有解的条件。此时方程为:

\[\begin{align}
&ax_1\times m+by_1\times m=gcd(a,b)\times m=c
\end{align}
\]

对于ax+by=gcd(a,b),其参数解为:

\[\begin{align}
&x=x_1+{b\over gcd(a,b)}\times t,y=y_1-{a\over gcd(a,b)}\times t,t为参数\\
&这里选用{b\over gcd(a,b)}和{a\over gcd(a,b)}是为了保证结果均为整数
\end{align}
\]

那么对比方程ax+by=c,可以得到其特解为:

\[\begin{align}
&x_0=x_1\times m=x_1\times {c\over gcd(a,b)}\\
&y_0=y_1\times m=y_1\times {c\over gcd(a,b)}
\end{align}
\]

那么其通解为:

\[\begin{align}
&X=x_0+{b\over gcd(a,b)}\times t,Y=y_0-{a\over gcd(a,b)}\times t,t为参数\\
&这里选用{b\over gcd(a,b)}和{a\over gcd(a,b)}作为系数是为了确保得到均为整数解。
\end{align}
\]

对于任意一个确定的t,都有一组确定的解与之对应,只需要根据需要找出对应的解即可。

例如求满足ax+by=c的X的最小非负整数解,那么在得到X的通解之后只需调整t,调整过程用伪代码表示为:

S = b / gcd(a, b);
X = (x0 % S + S) % S;
// 由于x0可能是负数,故在模之后还要加上S在取一次模。
// 同理可得Y的最小非负整数解
T = a / gcd(a, b);
Y = (y0 % T + T) % T;

完整的exgcd求解方程ax+by=c的X和Y的最小非负整数解得代码如下:

LL exgcd(LL a, LL b, LL c, LL& x, LL& y) {
if (b == 0) {
x = 1; y = 0;
return a;
}
LL ans = exgcd(a, b, c, y, x);
y -= a / b * x;
LL S = b / ans, T = a / ans;
x = (x % S + S) % S;
y = (y % T + T) % T;
return ans;
}

求关于x的方程ax≡b(mod m)的最小非负整数解

ax≡b(mod m)即ax%m=b%m,即求ax+my=b%m(取模其实就相当于减去(加上)了若干个模数)。但只有a和m互质时有唯一解,否则无解。转化为ax+my=b(假设b<m)后,套用exgcd即可。

求a关于模数p的乘法逆元

设x是a关于p的乘法逆元,那么有ax≡1(mod p),这是第二个应用的特殊情况,显然也可以通过类似方法求得。

逆元

Inverse Element,逆元,推广了加法中的加法逆元和乘法中的倒数。直观地说,它是一个可以取消另一给定元素运算的元素。a关于模p的逆元存在的条件是gcd(a,p)=1。

\[\begin{align}
&在模p意义下,设A的逆元为A^{-1},那么有A\times A^{-1}\equiv 1(mod\space p)
\end{align}
\]

为什么需要逆元呢?

\[\begin{align}
&在模意义下,{A\over B}\%p = {A\%p\over B\%p}\%p并不成立,\\
&那么设B在模p意义下的逆元表示为B^{-1},根据逆元的定义,有{A\over B}=A\times B^{-1}(mod \space p)。
\end{align}
\]

这里,我们把除法转化为乘法,就可以运用取模运算的性质:(a * b) % c = (a % c * b % c) % c,优化算法。

逆元的求法

EXGCD求法

给定模数m,求a的逆元相当于求解关于x的方程ax≡1(mod m)。这个方程可以转化为ax-my=1 。用EXGCD可以求得一组x,y和gcd。检查gcd是否为1(因为EXGCD是把问题转化为求解方程ax-my=gcd(a,m),显然只有gcd(a,m)=1时才能转化),gcd不为1则说明逆元不存在。在能够成功求解的情况下,把解调整x到0~m-1的范围中即可。

费马小定理

在模p为质数的情况下,有费马小定理:

\[a^{p-1}\equiv 1(mod\space p)
\]

那么:

\[a\times a^{p-2}\equiv 1(mod\space p)\\
故a的逆元a^{-1}=a^{p-2}
\]

然后用快速幂求出逆元即可。

拓展:快速幂

// a是底数,b是指数
LL pow(LL a, LL b, LL mod) {
LL ans = 1;
while(b) {
if(b&1) ans = ans * a % mod;
b >>= 1, a = a * a % mod;
}
return ans;
}

拓展:慢速乘,防止快速幂过程中乘法运算爆long long,只需把快速幂中的乘法替换为mul()即可,一般情况用不上。

// a为因数1,b为因数2
LL mul(LL a, LL b, LL mod) {
LL ans = 0;
while(b) {
if(b&1) ans = (ans + a) % mod;
b >>= 1, a = (a + a) % mod;
}
return ans;
}

欧拉定理

费马小定理其实是欧拉定理的特殊情况。在模数p不为质数时,有:

\[a^{\phi(p)}\equiv 1(其中\phi()是欧拉函数)
\]

同理可以得到:

\[a^{-1}\equiv a^{\phi(p)-1}
\]

线性逆元表

有时我们需要快速得出1~p-1的所有逆元,这个时候我们需要一种O(n)的方法,这就是线性逆元表。

首先,1关于任意模p的逆元的逆元都是1。设p = k * i + r(r < i ,1 < i < p),那么有:

\[k\times i + r \equiv0(mod\space p)
\]

利用逆元性质,两边同时乘上i的逆元以及r的逆元,得到:

\[k\times r^{-1}+i^{-1}\equiv 0(mod\space p)
\]

移项得:

\[i^{-1}\equiv-k \times r^{-1}\equiv -⌊{p\over i}⌋\times (p\%i)^{-1}(mod\space p)
\]

显然p%i是小于i的,那么我们就可以利用之前的结果在O(1)时间算出单独的逆元。

用代码表示即:

// inv[i]对应i的逆元
LL inv[maxn];
void CalInv() {
// 0没有逆元,故不初始化
inv[1] = 1;
for (int i = 2; i < maxn; i++)
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
}

利用

\[(a\times c)\% mod = (a\%mod\times c\%mod)\%mod;\\
由a\times a^{-1}\equiv1(mod \space p)得,\\
inv_{fac(i)}\equiv inv_{fac(i)}\times (i+1)^{-1}\times (i+1)\equiv inv_{fac(i+1)}\times (i+1)(mod\space p)
\]

我们还可以在线性时间求出1~min(n,p)的阶乘的逆元,代码如下:

LL fac[maxn], inv[maxn];
void CalFacInv() {
// 0的阶乘是1
fac[0] = fac[1] = 1;
for (int i = 2; i < maxn; i++)
fac[i] = fac[i - 1] * i % mod;
inv[maxn - 1] = QPow(fac[maxn - 1], mod - 2);
// 注意边界是≥
for (int i = maxn - 2; i >= 0; i--)
inv[i] = inv[i + 1] * (i + 1) % mod;
}

例题:hdu1576

题意即把除法转化为逆元乘法。3种方法的代码:

#include<iostream>
#include<algorithm>
using namespace std; typedef long long LL;
LL n, B;
const LL mod = 9973; LL exgcd(LL a, LL b, LL &x, LL &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
LL ans = exgcd(b, a % b, y, x);
y -= a/b*x;
return ans;
} LL QPow(LL x, LL n)
{
LL res = 1;
while(n)
{
if(n & 1) res = res * x % mod;
x = x * x % mod;
n >>= 1;
}
return res;
} LL inv[10000];
void CalInv() {
inv[1] = 1;
for (LL i = 2; i < 10000; i++) {
inv[i] = (mod * mod - mod / i * inv[mod % i]) % mod;
}
} int main() {
CalInv();
int t;
cin >> t;
while (t--) {
cin >> n >> B;
// exgcd
// LL x, y;
// exgcd(B, mod, x, y);
// cout << (x + mod) * n % mod << endl; // QPow
// cout << QPow(B, mod - 2) * n % mod << endl; // 逆元打表
cout << inv[B % mod] * n % mod << endl;
}
return 0;
}

Algorithm: GCD、EXGCD、Inverse Element的更多相关文章

  1. 【BZOJ1951】【SDOI2010】古代猪文 Lucas定理、中国剩余定理、exgcd、费马小定理

    Description “在那山的那边海的那边有一群小肥猪.他们活泼又聪明,他们调皮又灵敏.他们自由自在生活在那绿色的大草坪,他们善良勇敢相互都关心……” ——选自猪王国民歌 很久很久以前,在山的那边 ...

  2. 【板子】gcd、exgcd、乘法逆元、快速幂、快速乘、筛素数、快速求逆元、组合数

    1.gcd int gcd(int a,int b){ return b?gcd(b,a%b):a; } 2.扩展gcd )extend great common divisor ll exgcd(l ...

  3. nomasp 博客导读:Android、UWP、Algorithm、Lisp(找工作中……

    Profile Introduction to Blog 您能看到这篇博客导读是我的荣幸.本博客会持续更新.感谢您的支持.欢迎您的关注与留言.博客有多个专栏,各自是关于 Android应用开发 .Wi ...

  4. js便签笔记(3)——切记:appendChild()、insertBefore()是移动element节点!

    appendChild().insertBefore()是移动element节点,看书的时候注意过,也可以做一个简单的例子测试一下: <div id="div1"> & ...

  5. iOS开发:深入理解GCD 第二篇(dispatch_group、dispatch_barrier、基于线程安全的多读单写)

    Dispatch Group在追加到Dispatch Queue中的多个任务处理完毕之后想执行结束处理,这种需求会经常出现.如果只是使用一个Serial Dispatch Queue(串行队列)时,只 ...

  6. Redis系列(十):数据结构Set源码解析和SADD、SINTER、SDIFF、SUNION、SPOP命令

    1.介绍 Hash是以K->V形式存储,而Set则是K存储,空间节省了很多 Redis中Set是String类型的无序集合:集合成员是唯一的. 这就意味着集合中不能出现重复的数据.可根据应用场景 ...

  7. 堆的源码与应用:堆排序、优先队列、TopK问题

    1.堆 堆(Heap))是一种重要的数据结构,是实现优先队列(Priority Queues)首选的数据结构.由于堆有很多种变体,包括二项式堆.斐波那契堆等,但是这里只考虑最常见的就是二叉堆(以下简称 ...

  8. 最近集训的图论(思路+实现)题目汇总(内容包含tarjan、分层图、拓扑、差分、奇怪的最短路):

    (集训模拟赛2)抢掠计划(tarjan强) 题目:给你n个点,m条边的图,每个点有点权,有一些点是"酒吧"点,终点只能在"酒吧",起点给定,路可以重复经过,但点 ...

  9. iOS开发之多线程技术(NSThread、OperationQueue、GCD)

    在前面的博客中如果用到了异步请求的话,也是用到的第三方的东西,没有正儿八经的用过iOS中多线程的东西.其实多线程的东西还是蛮重要的,如果对于之前学过操作系统的小伙伴来说,理解多线程的东西还是比较容易的 ...

随机推荐

  1. [NOIp2014] luogu P1351 联合权值

    哎我博 4 了. 题目描述 无向连通图 GGG 有 nnn 个点,n−1n−1n−1 条边.点从 111 到 nnn 依次编号,编号为 iii 的点的权值为 WiW_iWi​,每条边的长度均为 111 ...

  2. bcache 状态/配置 文件详细介绍(翻译自官网)

    声明: 文中 斜体带下划线  的段落为翻译不够准确的段落 原文:https://www.kernel.org/doc/Documentation/bcache.txt 官网:https://bcach ...

  3. HDU 1198 Farm Irrigation(状态压缩+DFS)

    题目网址:http://acm.hdu.edu.cn/showproblem.php?pid=1198 题目: Farm Irrigation Time Limit: 2000/1000 MS (Ja ...

  4. i春秋DMZ大型靶场实验(四)Hash基础

    下载工具包  打开目标机 通过目录爆破发现 phpmyadmin    在登录位置尝试注入 返现 可以注入 直接上sqlmap    上 bp 代理抓包 sqlmap.py  -r  bp.txt  ...

  5. 16.Linux yum扩展

    1.列出yum源可用的软件仓库 [root@yinwucheng ~]# yum repolist [root@yinwucheng ~]# yum repolist all 查看所有的仓库  ``` ...

  6. 20.Linux进程管理-企业案例

    1.管理进程状态 当程序运行为进程后,如果希望停止进程,怎么办呢? 那么此时我们可以使用linux的kill命令对进程发送关闭信号.当然除了kill.还有killall,pkill 1.使用kill ...

  7. Distinct 实现自定义去重

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  8. konva canvas插件写雷达图示例

    最近,做了一个HTML5的项目,里面涉及到了雷达图效果,这里,我将react实战项目中,用到的雷达图单拎出来写一篇博客,供大家学习. 以下内容涉及的代码在我的gitlab仓库中:Konva canva ...

  9. python小例子(二)

    1.在函数里面修改全局变量的值 2.合并两个字典.删除字典中的值 3.python2和python3 range(1000)的区别 python2返回列表,python3返回迭代器 4.什么样的语言可 ...

  10. POJ2431 优先队列+贪心 - biaobiao88

    以下代码可对结构体数组中的元素进行排序,也差不多算是一个小小的模板了吧 #include<iostream> #include<algorithm> using namespa ...