数论专项复习(一)扩展欧几里得算法(exgcd)
简介
扩展欧几里得算法(简称扩欧,exgcd),用来求下列方程的一组解:
\]
算法介绍
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}\)。动态转移方程:
\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;
}
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;
}
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\),则无解。否则有解。
& \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;
}
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\):
\]
用 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;
}
数论专项复习(一)扩展欧几里得算法(exgcd)的更多相关文章
- 浅谈扩展欧几里得算法(exgcd)
在讲解扩展欧几里得之前我们先回顾下辗转相除法: \(gcd(a,b)=gcd(b,a\%b)\)当a%b==0的时候b即为所求最大公约数 好了切入正题: 简单地来说exgcd函数求解的是\(ax+by ...
- 扩展欧几里得算法(exGCD)学习笔记
@(学习笔记)[扩展欧几里得] 本以为自己学过一次的知识不会那么容易忘记, 但事实证明, 两个星期后的我就已经不会做扩展欧几里得了...所以还是写一下学习笔记吧 问题概述 求解: \[ax + by ...
- Codeforces 1106F Lunar New Year and a Recursive Sequence (数学、线性代数、线性递推、数论、BSGS、扩展欧几里得算法)
哎呀大水题..我写了一个多小时..好没救啊.. 数论板子X合一? 注意: 本文中变量名称区分大小写. 题意: 给一个\(n\)阶递推序列\(f_k=\prod^{n}_{i=1} f_{k-i}b_i ...
- 【POJ】2142 The Balance 数论(扩展欧几里得算法)
[题意]给定a,b,c,在天平左边放置若干重量a的砝码,在天平右边放置若干重量b的砝码,使得天平两端砝码差为c.设放置x个A砝码和y个B砝码,求x+y的最小值. [算法]数论(扩展欧几里德算法) [题 ...
- 扩展欧几里得算法(EXGCD)学习笔记
0.前言 相信大家对于欧几里得算法都已经很熟悉了.再学习数论的过程中,我们会用到扩展欧几里得算法(exgcd),大家一定也了解过.这是本蒟蒻在学习扩展欧几里得算法过程中的思考与探索过程. 1.Bézo ...
- gcd(欧几里得算法)与exgcd(扩展欧几里得算法)
欧几里得算法: 1.定义:gcd的意思是最大公约数,通常用扩展欧几里得算法求 原理:gcd(a, b)=gcd(b, a%b) 2.证明: 令d=gcd(a, b) => a=m*d,b=n ...
- 扩展欧几里得算法详解(exgcd)
一.前言 本博客适合已经学会欧几里得算法的人食用~~~ 二.扩展欧几里得算法 为了更好的理解扩展欧几里得算法,首先你要知道一个叫做贝祖定理的玄学定理: 即如果a.b是整数,那么一定存在整数x.y使得$ ...
- vijos1009:扩展欧几里得算法
1009:数论 扩展欧几里得算法 其实自己对扩展欧几里得算法一直很不熟悉...应该是因为之前不太理解的缘故吧这次再次思考,回看了某位大神的推导以及某位大神的模板应该算是有所领悟了 首先根据题意:L1= ...
- 『扩展欧几里得算法 Extended Euclid』
Euclid算法(gcd) 在学习扩展欧几里得算法之前,当然要复习一下欧几里得算法啦. 众所周知,欧几里得算法又称gcd算法,辗转相除法,可以在\(O(log_2b)\)时间内求解\((a,b)\)( ...
- 详解扩展欧几里得算法(扩展GCD)
浅谈扩展欧几里得(扩展GCD)算法 本篇随笔讲解信息学奥林匹克竞赛中数论部分的扩展欧几里得算法.为了更好的阅读本篇随笔,读者最好拥有不低于初中二年级(这是经过慎重考虑所评定的等级)的数学素养.并且已经 ...
随机推荐
- 8.websocket slef概念
self代表当前用户客户端与服务端的连接对象,比如两客户端发来了两个连接,我们可以把两个连接放在一起 # 定义全局变量 CONN_List = [] class LiveConsumer(Websoc ...
- 一、什么是celery
一.什么是Celery 1.1.celery是什么 celery是一个简单.灵活且可靠的,处理大量消息的分布式系统,专注于是心爱处理的异步任务队列,同事也支持任务调度. Celery的架构由三部分组成 ...
- linux重置密码
方法一: 进入grub菜单界面 按e键 在linux开头的行按ctrl+e 或者end跳到行尾,输入rd.break 按ctrl+x mount -o remount,rw /sysroot chro ...
- 微信小程序canvas 证件照制作
小程序制作证件照过程 利用canvas制作生活中常用的证件照,压缩图片,修改图片dpi.希望给大家带来方便. 证件照小程序制作要点 上传合适的图片,方便制作证件照 调用AI接口,将图像进行人像分割.这 ...
- 常用Python库整理
记录工作和学习中遇到和使用过的Python库. Target 四个Level 整理 Collect 学习 Learn 练习 Practice 掌握 Master 1. Python原生和功能增强 1. ...
- Perl引用
引用就是C语言中的指针,perl引用是一个标量类型可以指向变量.数组.哈希表(也叫关联数组)甚至子程序,可以应用在程序的任何地方. 在变量前面加一个\就得到了这个变量的一个引用 #!usr/bin/p ...
- 微信公众号调试经常报access_token is invalid or not latest rid
是因为我没有使用中控服务器,所以服务器上使用同一个appid和secret获取了access_token 调试的时候再重新获取了一个新的access_token,所以导致微信服务器发放了新的acces ...
- C温故补缺(四):GDB
gdb gdb是由GNU软件社区提供的C Debug工具 Pre 在调试前,需要先编译.c程序,且要加上-g使输出文件变得可调式 gcc test.c -g -o test 用gdb test来调试程 ...
- 前端学习 linux —— 软件安装(Ubuntu)
软件安装(Ubuntu) 本篇主要讲解 ubuntu 中软件的安装.apt 的源.内网部署案例(graylog 为例),最后是 python 开发准备. apt 和 rpm 在linux 第一篇我们知 ...
- CLion和动态链接库
目录 生成链接库 链接库的使用 生成链接库 创建一个library项目 在项目中写好自己的代码 cmakelist cmake_minimum_required(VERSION 3.21) proje ...