数论 欧几里得算法

§ 欧几里得算法

基本知识

最大公约数、最小公倍数

gcd(a,b) 表示 ab 的最大公约数

lcm(a,b) 表示 ab 的最小公倍数

  • 定理

    lcm(a,b)=(a*b)/gcd(a,b)

最大公约数的相关性质

  1. gcd(a,b)=gcd(b,a)
  2. gcd(a,b)=gcd(a,b)
  3. gcd(a,b)=gcd(b,ab)
  4. gcd(a,b)=gcd(b,a%b)
  5. gcd(a,0)=|a|
  6. gcd(a,ka)=|a|
  7. gcd(ma,mb)=m×gcd(a,b)
  8. gcd(a,b)=gcd(a+mb,b)
  9. gcd(ab,m)=gcd(a,m)×gcd(b,m)
  10. gcd(a,p)=1,gcd(b,p)=1gcd(a×b,p)=1

更相减损法(略)

Euclidean Algorithm

定义及原理

又称 辗转相除法

原理 gcd(a,b)=gcd(b,a%b)

用法

template<class T>inline T gcd(T a,T b){
    return b==0?a:gcd(b,a%b);
}

证明

d=gcd(a,b),dZ+a=md,b=nd,m,nZ+,gcd(m,n)=1 r=a%b r=aqb=mdqnd=d(mqn),(qZ+)gcd(b,r)=gcd(nd,d(mqn))=d×gcd(n,mqn)=gcd(a,b)×gcd(n,mqn) gcd(n,m)=1gcd(n,mqn)=1gcd(a,b)=gcd(b,a%b)=d                       

裴蜀定理

定义及原理

又名贝祖定理

a,bZ+, gcd(a,b)=d x,y 线:ax+by=c (x,y) , c%d=0, x,yZ,使 ax+by=d 

证明

 d=gcd(a,b) s  ax+by=s,x,yZ  r=a%sr=aqs,qZ x=1qx,y=qy r=ax+by 0r<s s  ax+by=s,x,yZ  r=0 a%s=0 b%s=0 sa,b d=ks,kZ+ ax+by=s(x,y) (kx,ky)

扩展欧几里得

定义

已知 a,b ,扩展欧几里得算法可以求出 gcd(a,b),x,yZ ,满足 ax+by=gcd(a,b)

用法

template<class T>inline T exgcd(T a,T b,T&x,T&y){
    if(b==0){
        x=1,y=0;return a;
    }
    T gcd=exgcd(b,a%b,y,x);
    y=y-a/b*x;
    return gcd;
}

解释

i) b=0gcd(a,b)=a  a×x+0×y=a x=1,y=0 ii) b>0 gcd(a,b)=gcd(b,a%b) (1)ax1+by1=gcd(a,b)(2)bx2+(a%b)y2=gcd(b,a%b) a%b=abab  gcd(a,b)=gcd(b,a%b) ax1+by1=bx2+(abab)y2=ay2+b(x2y2ab) x1=y2,y1=x2y2ab

由上面的推导可以知道:

当递归达到边界条件时 (b==0)x=1,y=0 是目标方程的一组解 (x2,y2)

在向上返回的过程中,对应方程的解也会发生改变

若上一层返回的解为 (x2,y2) ,则本层对应的解为 (y2,x2y2ab)

那么在递归的过程中使用 exgcd(b,a%b,y,x) 就可以让 x 直接获得解,然后只需要计算 y 的值就好了

C++ 中,整数除法默认向下取整,故可以写成 y=y-a/b*x 的形式

根据递归求解的正确性,可以确定递归结果是正确的

xy

两个定理

定理一:对于不定方程 ax+by=c,c%gcd(a,b)=0 ,一定存在整数解为 (cgcd(a,b)x,cgcd(a,b)y) (这个应该没必要证明了吧)

定理二:若 (x,y) 是不定方程 ax+by=gcd(a,b) 的一组整数解,则 (x+kbgcd(a,b),ykagcd(a,b)) 也是方程的一组整数解

                     d=gcd(a,b)  (x1,y1)(x2,y2) ax+by=d,(1)ax1+by1=d(2)ax2+by2=d(1)(2) a(x1x2)+b(y1y2)=0 ad(x1,x2)=bd(y1y2) d=gcd(a,b) gcd(ad,bd)=1 bd|(x1x2)(3) x1x2=kbdx1=x2+kbd(1),(3), a(x2kbd)+by1=d(4) ax2+kabd+by1=d(2),(4), dby2+kabd+by1=d y1=y2kad                   

应用扩展欧几里得求乘法逆元

定义

对于 ax1(mod p)xap

换言之,对于方程 ax+by=1,gcd(a,b)=1x[1,b)a 在模 b 意义下的逆元

用法

那么我们就可以用 exgcd 求解这个方程,这样就得到了逆元

//扩展欧几里得
template<class T>inline T gcd(T a,T b,T&x,T&y){
    if(b==0){
        x=1,y=0;return a;
    }
    T g=gcd(b,a%b,y,x);
    y=y-a/b*x;
    return g;
}
//求 a 在模 p 意义下的逆元
inline int sol(int a,int p){
    int x,y;
    int g=gcd(a,p,x,y);
    int ans=x,t=0;
    //确保 x in [1,p)
    while((ans=x+b/g*t)<1)t++;
    while((ans=x+b/g*t)>=p)t--;
    return ans;
}

这里用到了一个定理:

定理二:若 (x,y) 是不定方程 ax+by=gcd(a,b) 的一组整数解,则 (x+kbgcd(a,b),ykagcd(a,b)) 也是方程的一组整数解

作用是确保 x[1,p)

e.g.

给定 n 个正整数 aip=109+7 ,求

i=1n(ai1×998244353ni)(mod p)

:     ax1(mod p):x=1a(mod p) {an}  :{1an} {si}si=a1×a2××ai(1) si=si1×ai 1si=1si1×ai(2) 1si1=1si×ai(3) 1ai=si1si ai,si线 线1sn 线1si,1ai

#include<bits/stdc++.h>
using namespace std;
#define GO(u,v,i) for(int i=u;i<=v;i++)
template<class t>inline t fr(){
    register t num=0,dis=1;
    register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')dis=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){num=(num<<1)+(num<<3)+(ch^48);ch=getchar();}
    return num*dis;
}
template<class t>inline void fw(t num){
    if(num>9)fw(num/10);
    putchar(num%10+'0');
}
template<class t>inline void fw(t num,char ch){
    if(num<0)num=-num,putchar('-');
    fw(num);putchar(ch);
}
const int maxn=5e6+12;
typedef long long lld;
const lld p=1e9+7,k=998244353;
int n;
lld a[maxn],s[maxn],sns[maxn],an[maxn];
lld anss;
template<class t>inline t gcd(t a,t b,t&x,t&y){
    if(b==0){
        x=1,y=0;
        return a;
    }
    t g=gcd(b,a%b,y,x);
    y=y-a/b*x;
    return g;
}
template<class t>inline t power(t num,t tim){
    t ans=1;
    GO(1,tim,i)ans*=num;
    return ans;
}
inline void sol(){
    n=fr<int>();
    s[0]=1;//初始化
    GO(1,n,i){
        a[i]=fr<lld>();
        s[i]=(s[i-1]*a[i])%p;//根据公式(1)
    }
    lld x,y;
    lld g=gcd(s[n],p,x,y);
    lld ans=x,t=0;
    while((ans=x+p/g*t)<1)t++;
    while((ans=x+p/g*t)>=p)t--;
    sns[n]=ans;// 1/bn 已求出
    lld tmp=1;
    for(int i=n;i>0;i--){
        sns[i-1]=(sns[i]*a[i])%p;//根据公式(2)
        an[i]=(sns[i]*s[i-1])%p;//根据公式(3)
        anss+=(an[i]*tmp)%p;//题目要求
        tmp*=k;tmp%=p;//tmp就是k^(n-i)
    }
    fw(anss%p,'\n');
}
signed main(){
    sol();
    return 0;
}
posted @   Locked_Fog  阅读(124)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示
主题色彩