noip2012 同余方程 关于gcd及exgcd

来源:互联网 发布:sql declare 编辑:程序博客网 时间:2024/06/05 22:31

本文章部分参考及证明来源潘承洞《初等数论》
对于数论的话,先来一道简简单单的小题。

相信这个题大家有做过,题解也不差我一个

题面:求关于 x 的同余方程 ax ≡ 1 (mod b)的最小正整数解。

input  两个正整数a,b。

output 一个正整数解x 数据保证该方程一定有解。

数据范围:

对于 40%的数据,2 ≤b≤ 1,000;

对于 60%的数据,2 ≤b≤ 50,000,000;

对于 100%的数据,2 ≤a, b≤ 2,000,000,000。

数据范围是10^9 必须O(n)线性过

40分简单枚举

60分我也不知道怎么能得60分QWQ

100分 exgcd 或者 欧拉定理(在这道题目有点大材小用了就不做讲解)

先讲一下exgcd

gcd 又称辗转相除法 这个用来求它的最大公约数非常显然

但是我必须给出他作为定理的证明

引理 设a,b是两个给定的整数,a不为0,那么,一定存在唯一一组整数q和r 满足 b = a * q + r       0<=r<|a|.

prove:唯一性:若不然,还有整数q‘和r’满足b = a * q’ + r‘  0<=r’<|a|

不妨设r' >= r 二式相减得 r' - r = (q - q') * a    0<= r' - r<|a|

if (r' - r) > 0 那么有上式知道(r'-r)| |a| 所以 |a| <= r 但r' - r<|a|   矛盾

故必有r‘ = r 这就证明了它的唯一性下面证明它的存在性

当a|b时 取q = b/a r = 0

当a不整除b时 考察集合

T = {b - ka,k为整数}

显然的T中必有正整数,由最小自然数原理知T中必存在一个最小的正整数t0 = k0a > 0

只需证明t0 < |a|即可 因为a不整除b所以t0 != |a| 若t0 > |a|  那么 t1 = t0 - |a| > 0 显然可见 t1 ∈ T  故和t0 的最小性矛盾!

那么只需取q = k0 r = t0 就可以满足要求

Euclid算法  设u0,u1是两个给定的整数 u1 != 0 u1 !| u0 我们一定可以重复利用以上的引理得到下面k+1个等式

u0 = q0u1 + u2   0 < u2 < |u1|                 (1)

u1 = q1u2 + u3   0 < u3 < u2                    (2)

u2 = q2u3 + u4   0 < u4  < u3

.............................................

u[k-1] = q[k-1]u[k] + u[k+1] 0 < u[k+1] < u[k]

u[k] =  q[k]u[k+1]

prove: 对u0 u1 应用引理 由u1 !| u0知必有(1)式成立

如果u2 !| u1 就得到二式 一次这样做就有 |u1| > u2 > u3 > .........>u[j+1] > 0

由于小于u1的整数是有限的而且1整除任意整数 所以必会出现某一个k 使得u[k+1]|uk >= 1

证毕。

int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}

理论上while比递归好一些,不过都很基础我就懒得写了

好exgcd:

在Euclid的条件和符号下我们有以下结论

(i)    u[k+1] = (u0,u1)   显然的要么我们要欧几里得算法干嘛

好吧我还是说一下吧

都知道a|b b|a b = +-a                m!=0 a|b     then  ma|mb

由最后一式递推上去

(ii)    d|u0 且d|u1的充要条件是 d|u[k+1]

利用整除的传递性和这样的一条性质就是(a|b a|c 有 对任意的x,y都有 a|bx+cy)

带入上式 即可得(ii)

(iii)存在整数x0 x1 s.t. u[k+1] = x0u0 + x1u1  这就是拓展欧几里得

prove:有第k式知道u[k+1]可以表示为u[k] u[k-1]的线性组合

由第k-1式知道 可消去u[k] 把u[k+1] 表示成u[k-1] u[k-2]的线性组合 递推上去便可以得到(iii)

大多数人背exgcd 并不是exgcd很难理解而是gcd和我们证明的引理不懂

其实也不能叫引理啦,一个数论最基础很重要的定理
#include<iostream>
#include<cstdio>
using namespace std;
int exgcd(int a,int b,int &x,int&y);
int main(){
 int a,b,r,x,y;
 scanf("%d %d",&a,&b);
 r=exgcd(a,b,x,y);
 while(x<0){
  x+=b;
 }
 printf("%d",x);
    return 0;
}
int exgcd(int a,int b,int &x,int&y){
 if(b==0){
  x=1;y=0;
  return a;
 }
 int r=exgcd(b,a%b,x,y);
 int tmp=x;
 x=y;
 y=tmp-a/b*y;
 return r;
}  // 代码没什么好说的很简单

 

原创粉丝点击