简介

扩展欧几里得算法(简称扩欧,exgcd),用来求下列方程的一组解:

\[ax+by=(a,b)
\]

算法介绍

1. 裴蜀定理

裴蜀定理(Bézout's lemma):若 \(a\in \mathbb{Z},b \in \mathbb{Z}\) 且 \(a,b\) 不全为 \(0\),则关于 \(x,y\) 的方程 \(ax+by=(a,b)\) 一定有整数解。

证明:略(其实是不会)

2. 欧几里得定理

欧几里得定理:\((a,b)=(b,a \bmod b)\)。

证明:略(其实还是不会)

用途:著名的辗转相除法就是用欧几里得定理证明的。

3. Exgcd

伪代码:

Integer X,Y // The Solution of AX+BY=(A,B)
Exgcd(Integer A,Integer B)
If B equals 0
X = 1
Y = 0
Return
Else
Exgcd(B,A mod B)
X = X - A / B * Y
Swap X,Y
End

证明:

  • 当 \(b=0\) 时,显然有 \(x=1,y=0\),使得 \(xa+yb=(a,b)\)。
  • 当 \(b>0\),则 \((b,a \bmod b)\)。存在整数 \(x,y\),满足 \(bx+(a\bmod b)y=(b,a\bmod b)\)。
  • 令 \(x'=y,y'=x-\lfloor\dfrac{a}{b}\rfloor y\),\(ax'+by'=(a,b)\)。

代码:

int x,y;
void exgcd(int a,int b){
if(b==0){
x=1;
y=0;
}
else{
exgcd(b,a%b);
int tmp=x;
x=y;
y=tmp-a/b*y;
}
}

时间复杂度 \(O(\log n)\)。

例题

CF510D Fox And Jumping

给出 \(n\) 张卡片,分别有 \(l_i\) 和 \(c_i\)。在一条无限长的纸带上,你可以选择花 \(c_i\) 的钱来购买卡片 \(i\),从此以后可以向左或向右跳 \(l_i\) 个单位。问你至少花多少元钱才能够跳到纸带上全部位置。若不可能,输出 \(-1\)。

\(1 \leq n \leq 300,1 \leq l_i \leq 10^{9},1 \leq c_i \leq 10^{5}\)

首先,如果我们可以选出一些个卡片 \((l_i,c_i)\),使得全部的 \(c_i\) 互质(我的意思是 \((c_i,(c_{i+1},(c_{i+2},\cdots))) = 1\) )。因为裴蜀定理 \(c_ix+c_jy=1\) 有解,所以一定可以从位置 \(x\) 跳到 \(x \pm 1\)。

一开始,我的思路是能选择 \(c = 1\) 就选择,否则选择两个不同的。Hack 数据:

7
15015 10010 6006 4290 2730 2310 1
1 1 1 1 1 1 10

答案应该是 \(6\)(选择前 \(6\) 个)。

然后我们考虑 DP,设 \(f_{i,j}\) 为选择前 \(i\) 个,选择的数的 \(\gcd\) 是 \(j\) 的最小代价。显然,最后答案是 \(f_{n,1}\)。动态转移方程:

\[f_{i,(j,l_i)} = \left\{
\begin{aligned}
& \min(f_{i,(j,l_i)},f_{i-1,j} + c_i) & (j \neq l_i) \\
& \min(f_{i-1,(j,l_i)},c_i) & (j = l_i)
\end{aligned}
\right.
\]

无法通过本题,考虑优化。发现这个动态转移方程很像三角形不等式 \(\operatorname{dis}_{u}=\min(\operatorname{dis}_{u},\operatorname{dis}_{v}+W_{u,v})\),想到用最短路优化。

使用堆优化 Dijkstra 算法可以做到 \(O(n^{3})\),可以通过本题。

#include <bits/stdc++.h>
#define int long long
#define gcd __gcd
using namespace std; unordered_map<int, int> dis;
unordered_set<int> vis;
priority_queue<pair<int,int>, vector<pair<int,int> >, greater<pair<int,int> > > pq;
int n,l[305],c[305]; void dijkstra(){
pq.push({0,0});
dis[0]=0;
while(!pq.empty()){
int x = pq.top().second;
pq.pop();
if(x==1){
break;
}
if(vis.find(x) != vis.end()){
continue;
}
vis.insert(x);
for(int j=1;j<=n;j++){
int y = gcd(x,l[j]);
if(dis.find(y) == dis.end()){
dis[y] = INT_MAX;
}
if(dis[y]>dis[x]+c[j]){
dis[y]=dis[x]+c[j];
pq.push(make_pair(dis[y],y));
}
}
}
} signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>l[i];
}
for(int j=1;j<=n;j++){
cin>>c[j];
}
dijkstra();
if(dis.find(1)==dis.end()){
// Impossible
cout<<-1;
}
else{
cout<<dis[1];
}
return 0;
}

AC Record

UVA10104 Euclid Problem

多组数据。每组数据给出两个正整数 \(A,B\)。令 \(D = (A,B)\),求出关于 \(x,y\) 的方程 \(Ax+By=D\) 中 \(|x|+|y|\) 最小的整数解。输出 \(x,y,D\)。

\(1 \leq A,B \leq 10^{9}\)

Exgcd 的模板题。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std; int x,y; void exgcd(int a,int b){
if(b==0){
x=1;
y=0;
}
else{
exgcd(b,a%b);
int tmp=x;
x=y;
y=tmp-a/b*y;
}
} signed main(){
int a,b;
while(cin>>a>>b){
exgcd(a,b);
cout<<x<<' '<<y<<' '<<__gcd(a,b)<<'\n';
}
return 0;
}

AC Record

P1082 [NOIP2012 提高组] 同余方程

求关于 \(x\) 的同余方程 \(ax\equiv 1\pmod{b}\) 的最小正整数解。数据保证有解。(即,\(a\) 在模 \(b\) 意义下的最小逆元)

\(2 \leq a,b \leq 2 \times 10^{9}\)

互质的数才有逆元,所以 \((a,b)=1\)。

发现其实就是求 \(ax \equiv (a,b) \pmod{b}\),两边同时加上 \(by\),得到 \(ax+by\equiv (a,b) \pmod{b}\)。我们先用 exgcd 求 \(ax+by=(a,b)\) 中的 \(x\),然后直接求出 \(x \bmod b\) 即可。

代码:

#include <iostream>
#define int long long
using namespace std; int x,y; void exgcd(int a,int b){
if(b==0){
x=1;
y=0;
}
else{
exgcd(b,a%b);
int tmp=x;
x=y;
y=tmp-a/b*y;
}
} signed main(){
int a,b;
cin>>a>>b;
exgcd(a,b);
cout<<(x%b+b)%b;
return 0;
}

P2613 【模板】有理数取余

给出一个有理数 \(c=\frac{a}{b}\),求 \(c \bmod 19260817\) 的值。如果无解,输出 Angry!

对于所有数据,保证 \(0\leq a \leq 10^{10001}\),\(1 \leq b \leq 10^{10001}\),且 \(a, b\) 不同时是 \(19260817\) 的倍数。

显然,若 \(b=0\),则无解。否则有解。

\[\begin{aligned}
& \frac{a}{b} \pmod{19260817} \\
& = a \times b^{-1} \pmod{19260817}
\end{aligned}
\]

怎么求逆元,上一题已经告诉大家了。最后注意读入,读入的时候顺便取余以免高精。这样子是正确的(因为同余两边同时模一个与同余式两边互质的数,同余依然成立)

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std; int x,y;
const int MOD = 19260817;
int a,b; inline int read() {
int res = 0, ch = getchar();
while(!isdigit(ch) and ch != EOF){
ch = getchar();
}
while(isdigit(ch)) {
res = (res << 3) + (res << 1) + (ch - '0');
res %= MOD;
ch = getchar();
}
return res;
} void exgcd(int a,int b) {
if(b==0) {
x=1;
y=0;
} else {
exgcd(b,a%b);
int tmp=x;
x=y;
y=tmp-a/b*y;
}
} signed main() {
a=read(),b=read();
if(b==0) {
printf("Angry!");
return 0;
}
exgcd(b,MOD);
x=(x+MOD)%MOD;
cout<<a*x%MOD<<endl;
return 0;
}

AC Record

P5656 【模板】二元一次不定方程 (exgcd)

\(T\) 组数据,每组数据:

给定不定方程 \(ax+by=c\)。

若该方程无整数解,输出 \(-1\)。

若该方程有整数解,且有正整数解,则输出其正整数解的数量,所有正整数解中 \(x\) 的最小值,所有正整数解中 \(y\) 的最小值,所有正整数解中 \(x\) 的最大值,以及所有正整数解中 \(y\) 的最大值。

若方程有整数解,但没有正整数解,你需要输出所有整数解中 \(x\) 的最小正整数值, \(y\) 的最小正整数值。

正整数解即为 \(x, y\) 均为正整数的解,\(\boldsymbol{0}\) 不是正整数。

整数解即为 \(x,y\) 均为整数的解。

\(x\) 的最小正整数值即所有 \(x\) 为正整数的整数解中 \(x\) 的最小值,\(y\) 同理。

对于 \(100\%\) 的数据,\(1 \le T \le 2 \times {10}^5\),\(1 \le a, b, c \le {10}^9\)。

  • 首先,若 \((a,b) \not\mid c\),则该方程无解。
  • 当 \(x\) 变为最小正整数解时,\(y\) 最大,若 \(y<0\),则没有正整数解。
  • 当 \(y\) 变为最小正整数解时,\(x\) 最大,若 \(x<0\),则没有正整数解。
  • 此外有解。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std; int t,a,b,c,x,y; void exgcd(int a,int b){
if(b==0){
x=1,y=0;
return;
}
else{
exgcd(b,a%b);
int tmp=x;
x=y;
y=tmp-y*(a/b);
}
} signed main(){
cin>>t;
while(t--){
cin>>a>>b>>c;
if(c%__gcd(a,b) != 0){
cout<<-1<<'\n';
}
else{
int d = __gcd(a,b);
a/=d;b/=d;c/=d;
exgcd(a,b);
x*=c;y*=c;
if(x<=0){
int t=-x/b+1;
x=x%b+b;
y=y-t*a;
if(y<=0){
cout<<x<<' '<<((y%a)+a)<<'\n';
continue;
}
}
if(y<=0){
int t=-y/a+1;
y=y%a+a;
x=x-t*b;
if(x<=0){
cout<<((x%b)+b)<<' '<<y<<'\n';
continue;
}
}
int t=x/b;
x%=b;
if(x==0){
x=x+b;
--t;
}
y+=t*a;
int minx=x,maxy=y;
t=y/a;y%=a;
if(y==0){
y+=a;
t--;
}
x+=t*b;
cout<<((x-minx)/b+1)<<' ';
cout<<minx<<' ';
cout<<y<<' ';
cout<<x<<' ';
cout<<maxy<<'\n';
}
}
return 0;
}

P2054 [AHOI2005] 洗牌

对于扑克牌的一次洗牌是这样定义的,将一叠 \(N\)( \(N\) 为偶数)张扑克牌平均分成上下两叠,取下面一叠的第一张作为新的一叠的第一张,然后取上面一叠的第一张作为新的一叠的第二张,再取下面一叠的第二张作为新的一叠的第三张……如此交替直到所有的牌取完。

如果对一叠 \(6\) 张的扑克牌 1 2 3 4 5 6,进行一次洗牌的过程如下图所示:



从图中可以看出经过一次洗牌,序列 1 2 3 4 5 6 变为 4 1 5 2 6 3。当然,再对得到的序列进行一次洗牌,又会变为 2 4 6 1 3 5

如果给定长度为 \(N\) 的一叠扑克牌,并且牌面大小从 \(1\) 开始连续增加到 \(N\)(不考虑花色),对这样的一叠扑克牌,进行 \(M\) 次洗牌。求经过洗牌后的扑克牌序列中第 \(L\) 张扑克牌的牌面大小。

\(0<N≤10^{10},0≤M≤10^{10}\),且 \(N\) 为偶数。

对于一张原来在 \(n\) 的牌,他现在的位置 \(L\):

\[n \times 2^{M} \equiv L \pmod{n+1}
\]

现在反推 \(n\):

\[n \equiv L \times (2^{M})^{-1} \pmod{n+1}
\]

用 exgcd 求出 \(2^{M}\) 的逆元,然后直接算。需要龟速乘、快速幂和 __int128

代码:

#include <bits/stdc++.h>
#define int __int128
using namespace std; int n,m,l,x,y; inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
} void exgcd(int a,int b){
if(b==0){
x=1,y=0;
return;
}
else{
exgcd(b,a%b);
int tmp=x;
x=y;
y=tmp-y*(a/b);
}
} int pow(int a,int b,int mod) {
int ans=1;
for(; b; b>>=1,a=a*a%mod) {
if(b&1) {
ans=ans*a%mod;
}
}
return ans;
} int mul(int a,int b,int mod) {
int ans=0;
for(; b; b>>=1,a=(a+a)%mod) {
if(b&1) {
ans=(ans+a)%mod;
}
}
return ans;
} signed main(){
n=read();m=read();l=read();
exgcd(pow(2,m,n+1),n+1);
x=(x%(n+1)+(n+1))%(n+1);
cout<<(long long)(mul(x,l,n+1))<<'\n';
return 0;
}

AC Record

数论专项复习(一)扩展欧几里得算法(exgcd)的更多相关文章

  1. 浅谈扩展欧几里得算法(exgcd)

    在讲解扩展欧几里得之前我们先回顾下辗转相除法: \(gcd(a,b)=gcd(b,a\%b)\)当a%b==0的时候b即为所求最大公约数 好了切入正题: 简单地来说exgcd函数求解的是\(ax+by ...

  2. 扩展欧几里得算法(exGCD)学习笔记

    @(学习笔记)[扩展欧几里得] 本以为自己学过一次的知识不会那么容易忘记, 但事实证明, 两个星期后的我就已经不会做扩展欧几里得了...所以还是写一下学习笔记吧 问题概述 求解: \[ax + by ...

  3. Codeforces 1106F Lunar New Year and a Recursive Sequence (数学、线性代数、线性递推、数论、BSGS、扩展欧几里得算法)

    哎呀大水题..我写了一个多小时..好没救啊.. 数论板子X合一? 注意: 本文中变量名称区分大小写. 题意: 给一个\(n\)阶递推序列\(f_k=\prod^{n}_{i=1} f_{k-i}b_i ...

  4. 【POJ】2142 The Balance 数论(扩展欧几里得算法)

    [题意]给定a,b,c,在天平左边放置若干重量a的砝码,在天平右边放置若干重量b的砝码,使得天平两端砝码差为c.设放置x个A砝码和y个B砝码,求x+y的最小值. [算法]数论(扩展欧几里德算法) [题 ...

  5. 扩展欧几里得算法(EXGCD)学习笔记

    0.前言 相信大家对于欧几里得算法都已经很熟悉了.再学习数论的过程中,我们会用到扩展欧几里得算法(exgcd),大家一定也了解过.这是本蒟蒻在学习扩展欧几里得算法过程中的思考与探索过程. 1.Bézo ...

  6. gcd(欧几里得算法)与exgcd(扩展欧几里得算法)

    欧几里得算法: 1.定义:gcd的意思是最大公约数,通常用扩展欧几里得算法求 原理:gcd(a, b)=gcd(b, a%b) 2.证明: 令d=gcd(a, b)  =>  a=m*d,b=n ...

  7. 扩展欧几里得算法详解(exgcd)

    一.前言 本博客适合已经学会欧几里得算法的人食用~~~ 二.扩展欧几里得算法 为了更好的理解扩展欧几里得算法,首先你要知道一个叫做贝祖定理的玄学定理: 即如果a.b是整数,那么一定存在整数x.y使得$ ...

  8. vijos1009:扩展欧几里得算法

    1009:数论 扩展欧几里得算法 其实自己对扩展欧几里得算法一直很不熟悉...应该是因为之前不太理解的缘故吧这次再次思考,回看了某位大神的推导以及某位大神的模板应该算是有所领悟了 首先根据题意:L1= ...

  9. 『扩展欧几里得算法 Extended Euclid』

    Euclid算法(gcd) 在学习扩展欧几里得算法之前,当然要复习一下欧几里得算法啦. 众所周知,欧几里得算法又称gcd算法,辗转相除法,可以在\(O(log_2b)\)时间内求解\((a,b)\)( ...

  10. 详解扩展欧几里得算法(扩展GCD)

    浅谈扩展欧几里得(扩展GCD)算法 本篇随笔讲解信息学奥林匹克竞赛中数论部分的扩展欧几里得算法.为了更好的阅读本篇随笔,读者最好拥有不低于初中二年级(这是经过慎重考虑所评定的等级)的数学素养.并且已经 ...

随机推荐

  1. 8.websocket slef概念

    self代表当前用户客户端与服务端的连接对象,比如两客户端发来了两个连接,我们可以把两个连接放在一起 # 定义全局变量 CONN_List = [] class LiveConsumer(Websoc ...

  2. 一、什么是celery

    一.什么是Celery 1.1.celery是什么 celery是一个简单.灵活且可靠的,处理大量消息的分布式系统,专注于是心爱处理的异步任务队列,同事也支持任务调度. Celery的架构由三部分组成 ...

  3. linux重置密码

    方法一: 进入grub菜单界面 按e键 在linux开头的行按ctrl+e 或者end跳到行尾,输入rd.break 按ctrl+x mount -o remount,rw /sysroot chro ...

  4. 微信小程序canvas 证件照制作

    小程序制作证件照过程 利用canvas制作生活中常用的证件照,压缩图片,修改图片dpi.希望给大家带来方便. 证件照小程序制作要点 上传合适的图片,方便制作证件照 调用AI接口,将图像进行人像分割.这 ...

  5. 常用Python库整理

    记录工作和学习中遇到和使用过的Python库. Target 四个Level 整理 Collect 学习 Learn 练习 Practice 掌握 Master 1. Python原生和功能增强 1. ...

  6. Perl引用

    引用就是C语言中的指针,perl引用是一个标量类型可以指向变量.数组.哈希表(也叫关联数组)甚至子程序,可以应用在程序的任何地方. 在变量前面加一个\就得到了这个变量的一个引用 #!usr/bin/p ...

  7. 微信公众号调试经常报access_token is invalid or not latest rid

    是因为我没有使用中控服务器,所以服务器上使用同一个appid和secret获取了access_token 调试的时候再重新获取了一个新的access_token,所以导致微信服务器发放了新的acces ...

  8. C温故补缺(四):GDB

    gdb gdb是由GNU软件社区提供的C Debug工具 Pre 在调试前,需要先编译.c程序,且要加上-g使输出文件变得可调式 gcc test.c -g -o test 用gdb test来调试程 ...

  9. 前端学习 linux —— 软件安装(Ubuntu)

    软件安装(Ubuntu) 本篇主要讲解 ubuntu 中软件的安装.apt 的源.内网部署案例(graylog 为例),最后是 python 开发准备. apt 和 rpm 在linux 第一篇我们知 ...

  10. CLion和动态链接库

    目录 生成链接库 链接库的使用 生成链接库 创建一个library项目 在项目中写好自己的代码 cmakelist cmake_minimum_required(VERSION 3.21) proje ...