ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

BSGS-Junior·大步小步算法

2019-03-09 20:52:37  阅读:436  来源: 互联网

标签:Hash res 小步 Junior Step rm mod LL BSGS


本文原载于:http://www.orchidany.cf/2019/02/06/BSGS-junior/#more

\(\rm{0x01}\) \(\mathcal{Preface}\)

\(\rm{BSGS}(Baby~Step~Giant~Step)\), 大步小步法。当然也会被叫做拔山盖世北上广深算法……咳,这并不重要。形式化地讲, \(\rm{BSGS}\)算法主要用来解决以下问题 :

给定质数\(p\), 整数\(a, b\), \((a, p)=1\).求最小的非负整数\(x\), 使得\(a^x≡ b~(\mod p)\)

而首先我们知道的,是由欧拉定理\(a ^{\varphi(p)} ≡ 1 ~(\mod p)​\),并且我们还知道\(a^0=1≡1 ~(\mod p)​\),所以我们可以得出一个断言:

如果方程\(a^x≡ b~(\mod p)​\)有最小非负整数解,那么最小非负整数解一定在\([0, \varphi(p))​\)中 \(\qquad \qquad(1) ​\)

此处肉眼可以看出其循环节为\(\varphi(p)\),不再证明。

之后我们将以此为基础进行类似分块的操作——

\(\rm{0x02~~Baby~Step~Giant~Step}\)

首先我们记\(n=\sqrt {\varphi(p)}\),那么\(\forall x \in [0, \varphi(p))\), \(x = i\times m+j\), \(i \leq \lfloor \frac{p−1-m}{m} \rfloor,~~ 0≤j <m\) 。那么对于原方程我们可以把其改为:\[a^{i\cdot n+j}≡ b~(\mod p)\]移一下项就可以变成\[a^j ≡b \cdot a^{-i\cdot n} (\mod p)\]那么现在我们的策略是算出所有\(a^j\)来,在\(\mod p\) 意义下观察是否有一个\(i\)使得\(a^j ≡b \cdot a^{-i\cdot n} (\mod p)\)。我们称左边枚举\(a^j\)叫做小步\((\rm{Baby~Step})\), 称右边枚举\(b \cdot a^{-i\cdot n}\)叫做大步\(~(\rm{Giant~Step})\)

那么其实算法流程很明晰了,我们只需要循环两次、第一次记录的\(a^j\)用哈希表(\(STL\)自带\(unordered\)_ \(map\))记录一下即可。

inline LL expow(LL a, LL b, LL p){
    LL res = 1 ;
    while (b){
        if (b & 1) 
            (res *= a) %= p ;
        (a *= a) %= p, b >>= 1 ;
    }
    return res % p ;
}
inline void bsgs(LL x, LL y, LL p){
    P = ceil(sqrt(p)), Hash.clear(), Q = expow(x, -P + 2 *(p - 1), p) ;
    //a ^ (p-1) = 1 (mod p) => Q = a^(-P) = a ^(-P + p -1) ;
    for (LL i = 1, j = 0 ; j < P ; ++ j, (i *= x) %= p) 
        if (!Hash.count(i)) Hash[i] = j ; // Push them into hash_table
    for (LL i = y, j = 0  ; j <= P ; ++ j, (i *= Q) %= p) 
        if (Hash.count(i)){ cout << Hash[i] + j * P << endl ; return ; }
    cout << "-1" << endl ;
}

其中细节还是有的:

  • 计算sqrt时要上取整

  • 我们在求\(a^{-i\cdot n}​\)时用的底变量需要由费马小定理求快速幂得出。但是此时指数上可能为负数,所以我们选择加上一个模数,不影响结果。
  • 两次循环枚举的边界要注意有的是\(\leq\)有的是\(<\)
  • 算法还没开始时,要判断本身\(a\)是否可以被\(P\)整除。如果不特判这种情况的话,我们上面代码中的Q就会=0,从而在下面的第二个循环处出错——我们的hash[i]j不能同时为\(0\),从而输出错误的答案。

\(\rm{0x03}\) 例题

\(T1~\)\(LuoguP4028\)

裸题,但是有很多坑……或者说上面列举的细节都涵盖了qaq

#include <cmath>
#include <cstdio>
#include <iostream>
#include<tr1/unordered_map>

#define LL long long

using namespace std ; 
using namespace tr1 ; int T ;
LL A, B, M, P, Q ; unordered_map <LL, LL> Hash ;

inline LL expow(LL a, LL b, LL p){
    LL res = 1 ;
    while (b){
        if (b & 1) 
            (res *= a) %= p ;
        (a *= a) %= p, b >>= 1 ;
    }
    return res % p ;
}
inline void bsgs(LL x, LL y, LL p){
    P = ceil(sqrt(p)), Hash.clear(), Q = expow(x, -P + 2 *(p - 1), p) ;
    //a ^ (p-1) = 1 (mod p) => Q = a^(-P) = a ^(-P + p -1) ;
    for (LL i = 1, j = 0 ; j < P ; ++ j, (i *= x) %= p) 
        if (!Hash.count(i)) Hash[i] = j ; // Push them into hash_table
    for (LL i = y, j = 0  ; j <= P ; ++ j, (i *= Q) %= p) 
        if (Hash.count(i)){ cout << Hash[i] + j * P << endl ; return ; }
    cout << "Couldn't Produce!" << endl ;
}
inline LL qr(){
    LL res = 0 ; char c = getchar() ; while (!isdigit(c)) c = getchar() ;
    while (isdigit(c)) res = (res << 1) + (res << 3) + c - 48, c = getchar() ;
    return res ;
}
int main(){
    cin >> T ;
    while (T --){
        M = qr(), A = qr(), B = qr() ;
        if ((!(A % M == 0 && B))) bsgs(A, B, M) ;
        else cout << "Couldn't Produce!" << endl ; 
    }
    return 0 ;
}

\(T2~\) \(TJOI2007~Cute~Prime​\)

最裸最裸的、无特判的题……可以水一下双倍经验。

\(\mathfrak{writter: pks}\)

标签:Hash,res,小步,Junior,Step,rm,mod,LL,BSGS
来源: https://www.cnblogs.com/pks-t/p/10502916.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有