acm數論之歐幾里得gcd

1.歐幾里得定理

 

  1. 同餘定理的公式:(a+b)%mod=(a%mod+b%mod)%mod
  2. (a*b)%mod=(a%mod*b%mod)%mod
     
  3. 擴展歐幾里得也有本身的一個公式:a*x+b*y=gcd(a,b)
     
1.1定義:
gcd(a,b)=gcd(b,a%b)
1.2用法:
得到最大公約數與最小公倍數----gcd(a,b)*lcm(a,b)=a*b;
int gcd(int a,int b)   //最大公約數
{
    if(b==0)  return a;
    else return gcd(b,a%b);
}     
int lcm(int a,int b)  //最小公倍數
{
    return a/gcd(a,b)*b;    //防止溢出
}

2.擴展歐幾里得
2.1定義:
ax+by=gcd(a,b)=d,在已知a,b的狀況下求解出一組x,yhtml

2.2推導過程:
由於a%b=a-(a/b)b,  則有 d=bx1+[a-(a/b)b]y1=bx1+ay1-(a/b)by1=ay1+b(x1-a/by1),  故 x=y1,y=x1-a/by1ios

2.3性質:
1.若經過擴展歐幾里得求出一組特解(x0,y0),那麼有ax0+by0=d.ide

      則方程的通解爲,其中k爲任意整數   x0=d/exgcd()*x,y0=d/exgcd()*y;
      x=x0+k*(b/d)--------x = x0 +( b / gcd( a, b ) ) * k
      y=y0-k*(a/d)--------y = y0 - ( a / gcd( a, b ) ) * k
2.已知ax+by=d的解,對於ax+by=c的解,c爲任意正整數,只有當d|c時纔有解
    其通解爲
    x=(c/d)x0+k(b/d)
    y=(c/d)y0-k(a/d)函數

//擴展歐幾里得處理ax+by=gcd(a,b)=d解的問題 
int exgcd(int a,int b,int &x,int &y)  //獲得gcd(a,b)的值 
{
    if(b==0)
    {
        x=1,y=0;
        return a;
     } 
     int r=exgcd(b,a%b,x,y);
     int t=x; x=y; y=t-a/b*y;
     return r;//gcd(a,b)
}

2.4常見用法:測試

(1)求形如ax+by=c的通解,或從中選取某些特解
(2)求乘法逆元
(3)求解線性同餘方程spa

 

寫的超級好的博客:http://www.cnblogs.com/wkfvawl/p/9350867.html.net

解題思路code

1.先化成不定方程ax+by=c orm

2.a,b參數取正數,判斷a,b是否爲負數,爲負數就a,b,c都乘上-1,另外注意若a,b中只有一個是負數,那另外一個不須要乘-1,由於a,b自己是含未知數參數的。htm

3.r=exgcd(a,b,x,y),而後判斷是否有解採用if(c%r==0)

求特解:x0=x*c/r,   y0=y*c/r;

求通解:x=x0+b/r*c , y=y0-a/r*c (其中 t 爲整數)

求最小解:int s=b/r;    minx= (x0%s+s)%s;//最小解

2.4.1例題實例:

問題描述:對於 ax+by=c 的不定方程求通解或特解?

設 r=gcd(a,b),

若 c%r!=0 這此方程無整數解

若 c%r==0,特解爲x1=x0*c/r , y1=y0*c/r,通解 x=x1+b/r*t , y=y1-a/r*t (其中 t 爲整數)

不定方程的最小解:

r=exgcd(a,b,x,y);

x=x*c/r;
int s=b/r;
minx= (x%s+s)%s;//最小解

例題1:

題目連接:https://cn.vjudge.net/problem/HDU-2669

題目大意:0<a, b<=2^31,X>=0,給定a,b求知足X*a + Y*b = 1的最小正數X,以及與之對應的Y。

解題思路:求形如ax+by=c的最小解,x=x0+k*(b/d), y=y0-k*(a/d),從通解可看出,其中b / d 都正數,x 和 t成正比,當x0爲正數的時候 t == 0,有最小值;當x0爲負數的時候,x最小就是0,與橫軸相交,因此能夠求出 x0 + b / t * t = 0時候的 t,由於此時 t是整數,不必定是準確相交的那一個點,因此當前的x多是負數,若是爲負數,t++,取下一個整數點。

ac代碼:

 1 #include<iostream>
 2 using namespace std;
 3 //擴展歐幾里得處理ax+by=gcd(a,b)=d解的問題 
 4 int exgcd(int a,int b,int &x,int &y)  //獲得gcd(a,b)的值 
 5 {
 6     if(b==0)
 7     {
 8         x=1,y=0;
 9         return a;
10      } 
11      int r=exgcd(b,a%b,x,y);
12      int t=x; x=y; y=t-a/b*y;
13      return r;//gcd(a,b)
14 }
15 int main()
16 {
17     int a,b;
18     while(~scanf("%d%d",&a,&b))
19     {
20         int x,y;
21         int d=exgcd(a,b,x,y);
22         if(1%d)//有餘數 
23         {
24             cout<<"sorry"<<endl; 
25             continue;
26         }
27         else
28         {
29             //經過exgcd函數獲得特解 
30             x=x/d,y=y/d;
31             if(x>0)//因爲a,b爲正數,因此k=0取最小 
32             cout<<x<<" "<<y<<endl;
33             else
34             {//當x0爲負數的時候,x最小就是0,與橫軸相交, 但x多是負數,若是爲負數,取下一個整數點
35                 int k=(-1)*x/b*d;
36                 int kx=x+b/d*k;
37                 int ky=y-k*(a/d);
38                 if(kx<0)
39                 {
40                     kx=x+b/d*(k+1);
41                     ky=y-(k+1)*(a/d);
42                 }
43                 cout<<kx<<" "<<ky<<endl;                
44             }
45         }    
46     }
47     return 0; 
48  } 
View Code

 

例題2:

題目連接:https://cn.vjudge.net/problem/ZOJ-3593

題意:一我的要從A走到B  只能走a布、b步、(a+b)步,能夠往左或右走, 問 最少要走幾步,不能走到的輸出-1

題目思路:能夠設走x步a米的,走y部b米的,獲得式子:ax + by = B - A;  

經過擴展歐幾里德可得x和y

x、y的解集:X = x + b/gcd(a,b) * t;   Y = y - a /gcd(a,b) * t;

x與y最接近時爲答案,這樣的話c能夠取的更多,那麼步數會最小

它們

(1)X、Y同號時,即方向相同,能夠走a+b來減小步數,取max(X,Y)(這裏能夠畫X、Y的座標圖來幫助理解)

(2)X、Y異號時,即方向相反,步數絕對值相加,abs(X) + abs(Y)

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 using namespace std;
 5 const long long maxn = 0x3f3f3f3f;
 6 //#define maxn 0x3f3f3f3f
 7 long long exgcd(long long a,long long b,long long &x,long long &y)
 8 {
 9     if(b==0)
10     {
11         x=1,y=0;
12         return a;
13     }
14     long long r=exgcd(b,a%b,x,y);
15     long long t=x;x=y;y=t-a/b*y;
16     return r;
17 }
18 long long Abs(long long a)
19 {
20     if(a<0)
21     a=-1*a;
22     return a;
23 }
24 int main()
25 {
26     long long a,b,A,B;
27     long long ans;
28     int T;
29     cin>>T;
30     while(T--)
31     {
32         cin>>A>>B>>a>>b;
33         long long x,y;
34         long long h=exgcd(a,b,x,y);
35         if((B-A)%h!=0)
36             printf("-1\n");
37         else{
38             x=(B-A)/h*x;//註釋修改
39             y=(B-A)/h*y;//註釋修改
40             long long minx,miny;
41             long long k;
42             b=b/h;
43             a=a/h;
44             k=(y-x)/(b+a);//註釋修改
45             ans=maxn*maxn;   //註釋修改  這裏定義成maxn數據定義小了
46             for(int i=k-1;i<=k+1;i++)
47             {
48                 minx=x+b*i,miny=y-a*i;
49                 long long m;
50                 if(Abs(minx+miny)==Abs(minx)+Abs(miny))//說明x與y同號
51                     m=max(Abs(minx),Abs(miny));
52                 else
53                     m=Abs(minx)+Abs(miny);
54                 if(m<ans)
55                  ans=m;
56 //                if((minx>0&&miny>0)||(minx<0&&miny<0))//x與y同號
57 //                {
58 //                m=Abs(min(minx,miny))+Abs(minx-miny);//這句有問題的  測試這個存在數據Abs(min(minx,miny))+Abs(minx-miny)!=max(Abs(minx),Abs(miny));
59 //                }
60 //                 else
61 //                 m=Abs(minx)+Abs(miny);
62 //                 if(m<ans)
63 //                 ans=m;
64 
65             }
66             cout<<ans<<endl;
67         }
68     }
69 }
ac代碼

wa的心得或者能夠說是經驗:

1.maxn定義不一樣而wa:

const long long maxn = 0x3f3f3f3f;   //正確
//#define maxn 0x3f3f3f3f   錯誤

2.除法運算要考慮是否倍數除,能夠倍數除最好倍數除

x=(B-A)/h*x;  y=(B-A)/h*y;//正確

x=x/h*(B-A);  y=y/h*(B-A);  //錯誤

3.ans=maxn*maxn;   //註釋修改  這裏定義成maxn數據定義小了

3.前一個爲正確寫法,後一個爲錯誤寫法

分析:二者的不一樣在於在for循環裏面minx,miny,後一種不能保證i=k-1~k+1這裏面取得答案,由於多了一個參數。

1 b=b/h;
2 a=a/h;
3 k=(y-x)/(b+a);//註釋修改
4 for(int i=k-1;i<=k+1;i++)
5 minx=x+b*i,miny=y-a*i;
1 b=b;
2 a=a;
3 k=(y-x)/(b+a)*h;
4 for(int i=k-1;i<=k+1;i++)
5 minx=x+b*i/h,miny=y-a*i/h;

 

 

 

 

2.4.2例題實例:求乘法逆元

問題描述:求解一個數 a 對於另外一個數 m 的乘法逆元:a*x=1(mod m) 也即:a*x%m=1求x   也即a*x+m*y=1 

當gcd(a , m) != 1 ,a*x + m*y = 1沒有解

當1 % gcd(a , m) == 0,a*x + m*y = 1有解

x0=x*1/exgcd();

最小的解:x0 % m

x 的通解: x0 + m*t 

#include<iostream>
#include<cmath>
using namespace std;
//擴展歐幾里得處理ax+by=gcd(a,b)=d解的問題 
int exgcd(int a,int b,int &x,int &y)  //獲得gcd(a,b)的值 
{
    if(b==0)
    {
        x=1,y=0;
        return a;
     } 
     int r=exgcd(b,a%b,x,y);
     int t=x; x=y; y=t-a/b*y;
     return r;//gcd(a,b)
}
//求解一個數 a 對於另外一個數 m 的乘法逆元:a*x=1(mod m) 也即:a*x%m=1求x   也即a*x+m*y=1 
int inversenum(int a,int m)
{
    int x,y,ans,gcd;
    gcd=exgcd(a,m,x,y);
    if(1%gcd!=0)//無解 
    return -1;
    x=x*1/gcd;
    m=abs(m);
    ans=x%m;
    if(ans<=0)
    ans+=m;
    return ans;
}
int main()
{
    int a,m;
    cin>>a>>m;
    cout<<inversenum(a,m)<<endl;
 } 
例題:ZOJ3609 Modular Inverse
 1 #include<iostream>
 2 #include<cmath>
 3 using namespace std;
 4 //擴展歐幾里得處理ax+by=gcd(a,b)=d解的問題 
 5 int exgcd(int a,int b,int &x,int &y)  //獲得gcd(a,b)的值 
 6 {
 7     if(b==0)
 8     {
 9         x=1,y=0;
10         return a;
11      } 
12      int r=exgcd(b,a%b,x,y);
13      int t=x; x=y; y=t-a/b*y;
14      return r;//gcd(a,b)
15 }
16 //求解一個數 a 對於另外一個數 m 的乘法逆元:a*x=1(mod m) 也即:a*x%m=1求x   也即a*x+m*y=1 
17 int inversenum(int a,int m)
18 {
19     int x,y,ans,gcd;
20     gcd=exgcd(a,m,x,y);
21     if(1%gcd!=0)//無解 
22     return -1;
23     x=x*1/gcd;
24     m=abs(m);
25     ans=x%m;
26     if(ans<=0)
27     ans+=m;
28     return ans;
29 }
30 int main()
31 {
32     int T;
33     cin>>T;
34     while(T--){
35     int a,m;
36     cin>>a>>m;
37     if(inversenum(a,m)==-1)
38     cout<<"Not Exist"<<endl;
39     else
40     cout<<inversenum(a,m)<<endl;
41     }    
42  } 
View Code

 

2.4.2例題實例:求線性同餘方程

問題描述:關於 x 的模方程 ax%b=c (或者是(x+mt)%L=(y+nt)%L)的解,方程轉換爲 ax+by=c 其中 y 通常爲非正整數

解得 x1=x0*c/r,通解爲 x=x1+b/r*t

設 s=b/r (已證實 b/r 爲通解的最小間隔),則 x 的最小正整數解爲 (x1%s+s)%s

例題:poj 1061

根據題意:可寫出(x+mt)%L=(y+nt)%L,求可取的最小t

(m-n)*t=(y-x)%L;    (m-n)*t+L*b=y-x;   (m-n)*a+L*b=y-x;  當作a*m1+b*n1=t;(m1=m-n,n1=L,t=y-x) 

注意:數據類型應該爲long long

 1 #include<iostream>
 2 #include<cmath>
 3 using namespace std;
 4 //擴展歐幾里得處理ax+by=gcd(a,b)=d解的問題 
 5 long long t;
 6 long long exgcd(long long a,long long b,long long &x,long long &y)  //獲得gcd(a,b)的值 
 7 {
 8     if(b==0)
 9     {
10         x=1,y=0;
11         return a;
12      } 
13      long long r=exgcd(b,a%b,x,y);
14      long long t=x; x=y; y=t-a/b*y;
15      return r;//gcd(a,b)
16 }
17 long long solve(long long m,long long n)
18 {
19     long long x,y;
20     long long r=exgcd(m,n,x,y);
21     if(t%r)
22     {
23         return -1;
24     }
25     x=x*t/r;
26     long long s=n/r;
27     return (x%s+s)%s;//不定方程的最小解x 
28 }
29 int main()
30 {
31     long long x,y,m,n,L;
32     cin>>x>>y>>m>>n>>L;
33     //不定式a*(m-n)+b*L=y-x;   當作a*m+b*n=t; 
34     t=y-x;
35     m=m-n;
36     n=L;
37     if(m<0)//整個不定式a,b取正數,因爲b>0,b不須要變(b前面有係數),因此t須要改變 
38     {
39         m=-1*m;
40         t=-1*t;
41     }
42     if(solve(m,n)==-1)
43     cout<<"Impossible"<<endl;
44     else
45     {
46         cout<<solve(m,n)<<endl;
47     }
48     return 0;
49  } 
View Code

 

例題:p2421荒島野人

題目連接:https://www.luogu.org/problemnew/show/P2421

題目思路:暴力+exgcd

首先判斷兩個野人是否會在同一個洞穴:(Ci+Pi*t)%M==(Cj+Pj*t)%M    (p[i]-p[j])*t=(C[j]-C[i])%M     (P[i]-P[j])*a+M*b=C[j]-C[i]

暴力求M,從1遞增,知足條件就停下來,check(M)

check(M)的做用是判斷n個野人之間沒有住在同一個洞穴,即餘數不一樣或者是相同餘數數字大於兩野人最小壽命。

題目代碼:

 1 #include<iostream>
 2 #include<algorithm> 
 3 using namespace std;
 4 int c[200],p[200],l[200],n;
 5 int gcd(int a,int b){return a%b==0?b:gcd(b,a%b);}
 6 //擴展歐幾里得處理ax+by=gcd(a,b)=d解的問題 
 7 int exgcd(int a,int b,int &x,int &y)  //獲得gcd(a,b)的值 
 8 {
 9     if(b==0)
10     {
11         x=1,y=0;
12         return a;
13      } 
14      int r=exgcd(b,a%b,x,y);
15      int t=x; x=y; y=t-a/b*y;
16      return r;//gcd(a,b)
17 }
18 bool go(int i,int j,int b){
19       int a=p[i]-p[j],f=c[j]-c[i],g,x,y;
20       if(a<0)
21       {
22           a=a*-1,f=-1*f;
23       }
24       g=exgcd(a,b,x,y);
25       if(f%g==0){
26           x=x*f/g;
27           b=b/g;
28           x=(x%b+b)%b;//最小解
29           if(x<=min(l[i],l[j]))
30           return 0;
31       }
32       return 1;
33 }
34 bool check(int m){
35       int i,j,k;
36       for(i=1;i<n;i++)
37         for(j=i+1;j<=n;j++)
38           if(!go(i,j,m))return 0;
39       return 1;
40 }
41 int main()
42 {     int m=0,i,j,k;
43       scanf("%d",&n);
44       for(i=1;i<=n;i++)scanf("%d%d%d",&c[i],&p[i],&l[i]),m=max(m,c[i]);
45       while(m){
46           if(check(m)){
47             printf("%d\n",m);
48             return 0;
49         }
50           m++;
51       }
52       return 0;
53 }
View Code

 

題目集應用:http://www.cnblogs.com/yzxverygood/category/1232095.html

第二題:hdu 1576 https://cn.vjudge.net/problem/HDU-1576

題目思路:求(A/B)%9973,已知條件有:n=A%9973,gcd(B,9973)=1,A%B=0;輸入n,B,求(A/B)%9973 的值?

設(A/B)%9973=x,即(A/B)=9973h+x;  A=9973*h*B+x*B ;  n=A%9973即n=(9973*h*B+x*B)%9973,即n=(x*B)%9973,即x*B-9973y=n;求最小的正數x

 1 #include<iostream>
 2 using namespace std;
 3 //擴展歐幾里得處理ax+by=gcd(a,b)=d解的問題 
 4 int exgcd(int a,int b,int &x,int &y)  //獲得gcd(a,b)的值 
 5 {
 6     if(b==0)
 7     {
 8         x=1,y=0;
 9         return a;
10      } 
11      int r=exgcd(b,a%b,x,y);
12      int t=x; x=y; y=t-a/b*y;
13      return r;//gcd(a,b)
14 }
15 int main()
16 {
17     int T,n,B;
18     scanf("%d",&T);
19     while(T--)
20     {
21         scanf("%d%d",&n,&B);
22         int x,y;
23         int d=exgcd(B,9973,x,y);
24         x=x*n/d;//最小解 
25         int s=9973/d;
26         x=(x%s+s)%s;
27         cout<<x<<endl;
28     }
29     return 0; 
30  }
View Code

 

第三題:UVA12169   https://cn.vjudge.net/problem/UVA-12169

題目大意:有3個整數 x[1], a, b 知足遞推式x[i]=(a*x[i-1]+b)mod 10001。由這個遞推式計算出了長度爲2T的數列,如今要求輸入x[1],x[3],......x[2T-1], 輸出x[2],x[4]......x[2T]. T<=100,0<=x<=10000. 若是有多種可能的輸出,任意輸出一個結果便可。

題目思路:第一種直接暴力方法,枚舉a,b,找到合適的a,b(即知足式子,又由於咱們只知道x[1],x[3]..,因此只要它知足全部的x[i]=(a*((a*x[i-2]+b)mod 10001)+b)mod 10001)而後輸出相對於應的x[2],x[4]...

ac代碼:

 1 #include<iostream>
 2 using namespace std;
 3 #define maxn 100005
 4 int a[maxn];
 5 int main()
 6 {
 7     int n;
 8     scanf("%d",&n);
 9     for(int i=0;i<n;i++)
10     scanf("%d",&a[i]);
11     int flag=0;
12     for(int i=1;i<=10000;i++)
13     {
14     for(int j=1;j<=10000;j++)
15     {
16         flag=0;//i,j爲a,b   篩選知足式子的a,b 
17         for(int h=1;h<n;h++)
18         {
19             if(a[h]!=((i*((i*a[h-1]+j)%10001)+j)%10001))
20             {
21                 flag=1;
22                 break;
23             }
24         }
25         if(flag==0)
26         {
27             for(int h=0;h<n;h++)
28             {
29                 printf("%d\n",(i*a[h]+j)%10001);
30             }
31             break;
32         }
33     }
34     if(flag==0)
35     break;
36     }
37     return 0;
38 }
暴力法

第二種方法,比第一種方法快,咱們枚舉a,利用簡化式子(a+1)*b mod 10001 = (x[3]-a*a*x[1]) mod 10001。這裏就變成了同模方程,擴展歐幾里得便可解答,求b。而後由a,b求相對於應的x[2],x[4]...

相應博客:http://www.javashuo.com/article/p-dbpklgwp-hu.html

 

  • ZOJ3609
  • ZOJ3593
  • POJ1061
  • HDU1576
  • HDU2669
  • UVA12169

 

  1. 同餘定理的公式:(a+b)%mod=(a%mod+b%mod)%mod
  2.  
    (a*b)%mod=(a%mod*b%mod)%mod
  3.  
    擴展歐幾里得也有本身的一個公式:a*x+b*y=gcd(a,b)
相關文章
相關標籤/搜索