zoukankan      html  css  js  c++  java
  • 解高次同余方程 (A^x=B(mod C),0<=x<C)Baby Step Giant Step算法

    先给出我所参考的两个链接:

    http://hi.baidu.com/aekdycoin/item/236937318413c680c2cf29d4 (AC神,数论帝  扩展Baby Step Giant Step解决离散对数问题)

    http://blog.csdn.net/a601025382s/article/details/11747747

    Baby Step Giant Step算法:复杂度O( sqrt(C) )

    我是综合上面两个博客,才差不多懂得了该算法。 

    先给出AC神的方法:

    原创帖!转载请注明作者 AekdyCoin !

    【普通Baby Step Giant Step】

    【问题模型】
    求解
    A^x = B (mod C) 中 0 <= x < C 的解,C 为素数

    【思路】
    我们可以做一个等价
    x = i * m + j ( 0 <= i < m, 0 <=j < m) m = Ceil ( sqrt( C) )
    而这么分解的目的无非是为了转化为:
    (A^i)^m * A^j = B ( mod C)

    之后做少许暴力的工作就可以解决问题:
    (1) for i = 0 -> m, 插入Hash (i, A^i mod C)
    (2) 枚举 i ,对于每一个枚举到的i,令 AA = (A^m)^i mod C
    我们有
    AA * A^j = B (mod C)
    显然AA,B,C均已知,而由于C为素数,那么(AA,C)无条件为1
    于是对于这个模方程解的个数唯一(可以利用扩展欧几里得或 欧拉定理来求解)
    那么对于得到的唯一解X,在Hash表中寻找,如果找到,则返回 i * m + j
    注意:由于i从小到大的枚举,而Hash表中存在的j必然是对于某个剩余系内的元素X 是最小的(就是指标)
    所以显然此时就可以得到最小解

    如果需要得到 x > 0的解,那么只需要在上面的步骤中判断 当 i * m + j > 0 的时候才返回


    到目前为止,以上的算法都不存在争议,大家实现的代码均相差不大。可见当C为素数的时候,此类离散对数的问题可以变得十分容易实现。


    【扩展Baby Step Giant Step】

    【问题模型】
    求解
    A^x = B (mod C) 中 0 <= x < C 的解,C 无限制(当然大小有限制……)

    【写在前面】
    这个问题比较麻烦,目前网络上流传许多版本的做法,不过大部分已近被证明是完全错误的!

    这里就不再累述这些做法,下面是我的做法(有问题欢迎提出)

    下面先给出算法框架,稍后给出详细证明:

    (0) for i = 0 -> 50 if(A^i mod C == B) return i    O(50)
    (1)  d=0                D=1 mod C
    while((tmp=gcd(A,C))!=1)
    {
    if(B%tmp)return -1; // 无解!
    ++d;
    C/=tmp;
    B/=tmp;
    D=D*A/tmp%C;        //不理解处1
    }
    (2) m = Ceil ( sqrt(C) ) //Ceil是必要的     O(1)
    (3) for i = 0 -> m 插入Hash表(i, A^i mod C)  O( m)
    (4) K=pow_mod(A,m,C)
    for i = 0 -> m
    解 D * X = B (mod C) 的唯一解 (如果存在解,必然唯一!)
    之后Hash表中查询,若查到(假设是 j),则 return i * m + j + d    //不理解处2
    否则
    D=D*K%C,继续循环
    (5) 无条件返回 -1 ;//无解!

    上面有两处我一开始不理解的地方,稍后我会解释,先把AC神的方法给copy完

    下面是证明:
    推论1:
    A^x = B(mod C)
    等价为
    A^a * A^b = B ( mod C) (a+b) == x a,b >= 0

    证明:
    A^x = K * C + B (模的定义)
    A^a * A^b = K*C + B( a,b >=0, a + b == x)
    所以有
    A^a * A^b = B(mod C)
    推论 2:
    令 AA * A^b = B(mod C)
    那么解存在的必要条件为: 可以得到至少一个可行解 A^b = X (mod C)
    使上式成立
    推论3
    AA * A^b = B(mod C)
    中解的个数为 (AA,C)
    由推论3 不难想到对原始Baby Step Giant Step的改进
    For I = 0 -> m
    For any solution that AA * X = B (mod C)
    If find X
    Return I * m + j+d(这里原本少了d
    而根据推论3,以上算法的复杂度实际在 (AA,C)很大的时候会退化到几乎O(C)
    归结原因,是因为(AA,C)过大,而就是(A,C)过大
    于是我们需要找到一中做法,可以将(A,C)减少,并不影响解

    下面介绍一种“消因子”的做法

    一开始D = 1 mod C
    进行若干论的消因子,对于每次消因子
    令 G = (A,C[i]) // C[i]表示经过i轮消因子以后的C的值
    如果不存在 G | B[i] //B[i]表示经过i轮消因子以后的B的值
    直接返回无解
    否则
    B[i+1] = B[i] / G
    C[i+1] = C[i] / G
    D = D * A / G

    具体实现只需要用若干变量,细节参考代码

    假设我们消了a'轮(假设最后得到的B,C分别为B',C')
    那么有
    D * A^b = B' (mod C')

    于是可以得到算法

    for i = 0 -> m
    解 ( D* (A^m) ^i ) * X = B'(mod C')
    由于 ( D* (A^m) ^i , C') = 1 (想想为什么?)
    于是我们可以得到唯一解
    之后的做法就是对于这个唯一解在Hash中查找

    这样我们可以得到b的值,那么最小解就是a' + b !!

    现在问题大约已近解决了,可是细心看来,其实还是有BUG的,那就是
    对于
    A^x = B(mod C)
    如果x的最小解< a',那么会出错
    而考虑到每次消因子最小消 2
    故a'最大值为log(C)
    于是我们可以暴力枚举0->log(C)的解,若得到了一个解,直接返回
    否则必然有 解x > log(C)

    PS.以上算法基于Hash 表,如果使用map等平衡树维护,那么复杂度会更大

    接下来我将讲解下面两处我一开始不理解的地方:

    (0) for i = 0 -> 50 if(A^i mod C == B) return i    O(50)
    (1)  d=0                D=1 mod C
    while((tmp=gcd(A,C))!=1)
    {
    if(B%tmp)return -1; // 无解!
    ++d;
    C/=tmp;
    B/=tmp;
    D=D*A/tmp%C;        //不理解处1
    }
    (2) m = Ceil ( sqrt(C) ) //Ceil是必要的     O(1)
    (3) for i = 0 -> m 插入Hash表(i, A^i mod C)  O( m)
    (4) K=pow_mod(A,m,C)
    for i = 0 -> m
    解 D * X = B (mod C) 的唯一解 (如果存在解,必然唯一!)
    之后Hash表中查询,若查到(假设是 j),则 return i * m + j + d    //不理解处2
    否则
    D=D*K%C,继续循环
    (5) 无条件返回 -1 ;//无解!

    后来看了上面给的第二个博客中的解释(被作者附在代码的后面),瞬间明白了啊!!!

    下面给出那个博客里写的内容,做些适当改动,看着舒服点:


    求解模方程A^x=B(mod C),C不为素数。拓展Baby Step Giant Step
    (该作者的方法一开始没有暴力先枚举答案,即没有AC神的第(0)步,直接从第(1)步开始了。)
    初始D=1,d=0,i=0;
    1.令tmp=gcd(A,C),若tmp==1则执行下一步。否则由于A^x=k*C+B;(k为某一整数),则(A/tmp)*A^(x-1)=k*(C/tmp)+B/tmp,(B/tmp为整除,若不成立则无解)
    令C=C/tmp,D=D*A/tmp,B=B/tmp,d++。则D*A^(x-d)=B(mod C),接着重复1步骤。
    看到没,while循环的原因瞬间明白了不?d的含义瞬间懂了有木有!!!也就是后面求DX=B(mod C)的解其实是x-d!!!所以最后才要i*m+j+d,加上个d!!!
    2.通过1步骤后,保证了A和D都与C互质,方程转换为D*A^(x-d)=B(mod C)。由于A和C互质,所以由欧拉定理A^phi(C)==1(mod C),(A,C互质)
    可知,phi(C)<=C,A^0==1(mod C),所以构成循环,且循环节不大于C。从而推出如果存在解,则必定1<=x<C。(在此基础上我们就可以用
    Baby Step Giant Step方法了)
    3.令m=ceil(sqrt(C)),则m*m>=C。用哈希表存储A^0,A^1,..,A^(m-1),接着判断1~m*m-1中是否存在解。
    4.为了减少时间,所以用哈希表缩减复杂度。分成m次遍历,每次遍历A^m长度。由于A和D都与C互质,所以gcd(D,C)=1。
    接下来实际求DX=B(mod C)的解。用拓展的欧几里德定理求得DX=1(mod C),即D*x+C*y=gcd(D,C)=1的一组整数解(x,y)。
    则D*x+C*y=1-->D*x%C=(D*x+C*y)%C=1-->D*(x*B)%C=((D*x)%C*B%C)%C=B。
    所以最终DX=B(mod C)的解X=x*B。若X在哈希表中存在,值为j,则D*A^j=B(mod C),最后我们要求解的答案就是ans=i*m+j+d。
    如果不存在,则令D=D*A^m%C,i++后遍历下一个A^m,直到遍历(A^m)^0到(A^m)^(m-1)还未找到,则说明不解并退出。

    本人注明:该博客中i循环是0~m-1,在AC神的方法中是0~m,本人觉得应该前者比较好。因为解x是1<=x<C的,而m=ceil(sqrt(C)),如果i=m的话,那m*m>C,显然不正确。

    下面是AC神的两道题的代码:

    HDU 2815

    #include<iostream>
    #include<map>
    #include<cmath>
    using namespace std;
    typedef long long LL;
    int gcd(int a,int b){return b?gcd(b,a%b):a;}
    int ext_gcd(int a,int b,int& x,int& y){
    int t,ret;
    if (!b){x=1,y=0;return a;}
    ret=ext_gcd(b,a%b,x,y);
    t=x,x=y,y=t-a/b*y;
    return ret;
    }
    int Inval(int a,int b,int n){
    int x,y,e;
    ext_gcd(a,n,x,y);
    e=(LL)x*b%n;
    return e<0?e+n:e;
    }
    int pow_mod(LL a,int b,int c){LL ret=1%c;a%=c;while(b){if(b&1)ret=ret*a%c;a=a*a%c;b>>=1;}return ret;}
    int BabyStep(int A,int B,int C){
    map<int,int> Hash;
    LL buf=1%C,D=buf,K;
    int i,d=0,tmp;
    for(i=0;i<=100;buf=buf*A%C,++i)if(buf==B)return i;
    while((tmp=gcd(A,C))!=1)
    {
    if(B%tmp)return -1;
    ++d;
    C/=tmp;
    B/=tmp;
    D=D*A/tmp%C;
    }
    Hash.clear();
    int M=(int)ceil(sqrt((double)C));
    for(buf=1%C,i=0;i<=M;buf=buf*A%C,++i)if(Hash.find((int)buf)==Hash.end())Hash[(int)buf]=i;
    for(i=0,K=pow_mod((LL)A,M,C);i<=M;D=D*K%C,++i)
    {
    tmp=Inval((int)D,B,C);
    if(tmp>=0&&Hash.find(tmp)!=Hash.end())return i*M+Hash[tmp]+d;
    }
    return -1;
    }
    int main()
    {
    int A,B,C;
    while(scanf("%d%d%d",&A,&C,&B)!=EOF)
    {
    if(B>=C){puts("Orz,I can’t find D!");continue;}
    int tmp=BabyStep(A,B,C);
    if(tmp<0)puts("Orz,I can’t find D!");else printf("%d
    ",tmp);
    }
    return 0;
    }
    View Code

    POJ 3243 Clever Y

    #include<iostream>
    #include<map>
    #include<cmath>
    using namespace std;
    typedef long long LL;
    const int maxn = 65535;
    struct hash{
    int a,b,next;
    }Hash[maxn << 1];
    int flg[maxn + 66];
    int top,idx;
    void ins(int a,int b){
    int k = b & maxn;
    if(flg[k] != idx){
    flg[k] = idx;
    Hash[k].next = -1;
    Hash[k].a = a;
    Hash[k].b = b;
    return ;
    }
    while(Hash[k].next != -1){
    if(Hash[k].b == b) return ;
    k = Hash[k].next;
    }
    Hash[k].next = ++ top;
    Hash[top].next = -1;
    Hash[top].a = a;
    Hash[top].b = b;
    }
    int find(int b){
    int k = b & maxn;
    if(flg[k] != idx) return -1;
    while(k != -1){
    if(Hash[k].b == b) return Hash[k].a;
    k = Hash[k].next;
    }
    return -1;
    }
    int gcd(int a,int b){return b?gcd(b,a%b):a;}
    int ext_gcd(int a,int b,int& x,int& y){
    int t,ret;
    if (!b){x=1,y=0;return a;}
    ret=ext_gcd(b,a%b,x,y);
    t=x,x=y,y=t-a/b*y;
    return ret;
    }
    int Inval(int a,int b,int n){
    int x,y,e;
    ext_gcd(a,n,x,y);
    e=(LL)x*b%n;
    return e<0?e+n:e;
    }
    int pow_mod(LL a,int b,int c){LL ret=1%c;a%=c;while(b){if(b&1)ret=ret*a%c;a=a*a%c;b>>=1;}return ret;}
    int BabyStep(int A,int B,int C){
    top = maxn; ++ idx; 
    LL buf=1%C,D=buf,K;
    int i,d=0,tmp;
    for(i=0;i<=100;buf=buf*A%C,++i)if(buf==B)return i;
    while((tmp=gcd(A,C))!=1){
    if(B%tmp)return -1;
    ++d;
    C/=tmp;
    B/=tmp;
    D=D*A/tmp%C;
    }
    int M=(int)ceil(sqrt((double)C));
    for(buf=1%C,i=0;i<=M;buf=buf*A%C,++i)ins(i,buf);
    for(i=0,K=pow_mod((LL)A,M,C);i<=M;D=D*K%C,++i){
    tmp=Inval((int)D,B,C);int w ;
    if(tmp>=0&&(w = find(tmp)) != -1)return i*M+w+d;
    }
    return -1;
    }
    int main(){
    int A,B,C;
    while(scanf("%d%d%d",&A,&C,&B)!=EOF,A || B || C){
    B %= C;
    int tmp=BabyStep(A,B,C);
    if(tmp<0)puts("No Solution");else printf("%d
    ",tmp);
    }
    return 0;
    }
    View Code
  • 相关阅读:
    C++面向对象编程,继承,数据抽象,动态绑定
    我也有自己的博客了
    使用 matlab 绘制饼状统计图
    让 matlab 程序发出声音
    多版本 python 使用 pip 安装第三方库
    从零开始学Python07作业思路:模拟人生小游戏
    从零开始学Python第七周:面向对象进阶(需修改)
    从零开始学Python06作业源码(仅供参考)
    从零开始学Python06作业思路:学生选课系统
    从零开始学Python第六周:面向对象基础(需修改)
  • 原文地址:https://www.cnblogs.com/chenxiwenruo/p/3554885.html
Copyright © 2011-2022 走看看