Baby Step Giant Step(好奇怪的名字)及其扩展: 求离散对数

定义

关于离散对数,请移步至此 => 离散对数:这个好难。。。
Baby Step Giant Step 中文名叫”大步小步算法”,用来求解如下同余方程x的最小正整数解:

axb(modp)0<=x<p a x ≡ b ( mod p ) 其 中 0 <= x < p

//这里 p p 为素数, abp a 、 b 、 p 已知,且 0<=a,b<p 0 <= a , b < p (如果a, b 大于p,可将它们对p取模)。
//显然若a为p的一个原根,则x即为b关于a的离散对数(mod p), 故此算法可求离散对数。

原理 将x重写为 x=im+j x = i ∗ m + j
其中 m=p,i=x/m,j=x%m m = ⌈ p ⌉ , i = x / m , j = x % m
显然 0i<m,0j<m 0 ≤ i < m , 0 ≤ j < m
那么我们就可以分别枚举i和j,以找到符合条件的x。
这里采用的方法是,先枚举j,将其所有结果放入一个哈希表中,然后再枚举i,此时对每个i都看一下能不能在哈希表里找到一个满足条件的j,这样复杂度就降下来了, 有一种分块的思想。

专业解释详见 =>维基百科Baby Step Giant Step
思想 分块 哈希 中途相遇
方法 hash_table 扩展gcd

步骤

  1. m=p m = ⌈ p ⌉
  2. 枚举 j j , j j 的范围是[0, .., m)。
     将 ajmodp a j mod p 作为键存入hash表,其值为j。
  3. 枚举 i i , i i 的范围是[0, .., m)。
     设 A=(am)i A = ( a m ) i 是已知(代入i), 设未知数j,满足 A(aj)B(modp) A ∗ ( a j ) ≡ B ( mod p ) ,
     那么j即为第二步枚举的j, 而 aj a j 即为第二步放入hash表内的值,由扩展欧几里德可求出 aj a j 的值。
     在hash表里找求出的 aj a j 的值,如果找到了则记录j的值,跳出此循环。
  4. 最后的结果即为 im+j i ∗ m + j

实现

/*==================================================*\
| Baby-Step-Giant-Step 大步小步算法
| 求 a^x === b (mod p) 中的 x值 -- 此处p仅为素数
| 实际运用中用自己的hash表代替map可防TLE
\*==================================================*/
LL BSGS(LL a, LL b, LL p) {
    a %= p; b %= p;
    map h;
    LL m = ceil(sqrt(p)), x, y, d, t = 1, v = 1;
    for(LL i = 0; i < m; ++i) {
        if(h.count(t)) h[t] = min(h[t], i);
        else h[t] = i;
        t = (t*a) % p;
    }
    for(LL i = 0; i < m; ++i) {
        d = extgcd(v, p, x, y);
        x = (x* b/d % p + p) % (p);
        if(h.count(x)) return i*m + h[x];
        v = (v*t) % p;
    }
    return -1;
}

扩展

在上述BSGS算法的基础上, 可进一步讨论p为合数的情况,作出如下改动即可:

  1. 从0开始在小范围内枚举结果,万一找到了呢。
  2. 此时 (a,p)1 ( a , p ) ≠ 1 , 所以要拿出来一些a和p进行约分, 直到 (a,p)=1 ( a , p ) = 1 , 约分过程在后面给出(此处为重点!!!)。
  3. 约分后 (a,p)=1 ( a , p ) = 1 , 可按照基本BSGS算法处理后续步骤。

约分过程如下

初始方程为

axb(modp) a x ≡ b ( mod p )

b=b,p=p b ′ = b , p ′ = p , 初始化 v=1 v = 1 ,每次约分拿出一个a, 令
d=gcd(a,p) d = g c d ( a , p ′ )

如果 d=1 d = 1 ,则约分过程结束, 约分完成。
否则的话,如果 d d 不能整除 b b ′ ,说明无法完成约分, x x 无解,整个算法结束。
再否则的话,更新 b,p,v b ′ , p ′ , v
b=bdp=pdv=vad b ′ = b ′ d , p ′ = p ′ d , v = v ∗ a d

然后继续循环进行下一轮约分。
其中 v v 即为 a a 每次约分剩下的没有被约掉的因子 的乘积。
约分完成后, 假设约分了cnt次,则程变为
vaxcntb(modp) v ∗ a x − c n t ≡ b ′ ( mod p ′ )

此时 (a,p)=1 ( a , p ′ ) = 1 ,显然我们可用基本的BSGS算法求得 xcnt x − c n t 的值,算法至此结束。
具体可参考下方的实现理解。

实现

/*==================================================*\
| 扩展BSGS
| 求 a^x === b (mod p) 中的 x值 -- 此处p可为合数
| 实际运用中用自己的hash表代替map可防TLE
| 未解之谜: a, b, p 分别为1, 1, 1时, 结果x为1而非0
\*==================================================*/
LL exBSGS(LL a, LL b, LL p) {
    a %= p; b %= p;
    LL ret = 1;
    for(LL i = 0; i <= 50; ++i) {
        if(ret == b) return i;
        ret = (ret*a) % p;
    }//枚举比较小的i

    LL x,y,d, v = 1, cnt = 0;
    while((d = gcd(a, p)) != 1) {
        if(b % d) return -1;
        b /= d, p /= d;
        v = (v * (a/d)) % p;
        ++cnt;
    }//约分直到(a, p) == 1

    map h;
    LL m = ceil(sqrt(p)), t = 1;
    for(LL i = 0; i < m; ++i) {
        if(h.count(t)) h[t] = min(h[t], i);
        else h[t] = i;
        t = (t*a) % p;
    }
    for(LL i = 0; i < m; ++i) {
        d = extgcd(v, p, x, y);
        x = (x* (b/d) % p + p) % p;
        if(h.count(x)) return i*m + h[x] + cnt;
        v = (v*t) % p;
    }
    return -1;
}

应用

求离散对数

Discrete Logging POJ - 2417
Power Modulo Inverted SPOJ - MOD

你可能感兴趣的:(数论,知识点)