一、离散对数
给定 \(a,b,m\),存在一个 \(x\),使得
\(\displaystyle a^x\equiv b\pmod m\)
则称 \(x\) 为 \(b\) 在模 \(m\) 意义下以 \(a\) 为底的 离散对数。
二、BSGS
离散对数:求解关于 \(x\) 的方程 \(a^x\equiv b\pmod m\)。
基本思想:(假设 \(\gcd(a,m)=1\),那么 \(a\) 在模 \(m\) 意义下存在逆元)
考虑类似分块的一个想法。首先设定一个常量 \(t\)。
设 \(x=qt+r\)(\(0\leq r<t\)),预处理所有 \(a^{qt}\) 模 \(m\) 的值,存到 Hash 表 / map 中。询问时,枚举 \(r\),因为 \(a^{qt+r}\equiv b\pmod m\Leftrightarrow a^{qt}\equiv b\cdot a^{-r}\pmod m\),所以判断是否存在 \(a^{qt}\equiv b\cdot a^{-r}\pmod m\) 即可。
预处理的复杂度为 \(\mathcal{O}(\frac{m}{t})\),单次询问的复杂度为 \(\mathcal{O}(t)\)。取 \(t=\sqrt{m}\),则复杂度为 \(\mathcal{O}(\sqrt{m})\)。
用 map 会多一个 \(\log\)。
不同的写法:如果不想求 \(a^{-r}\),设 \(x=qt-r\)(\(0\leq q,r<t\)), \(a^{qt-r}\equiv b\pmod m\Leftrightarrow a^{qt}\equiv b\cdot a^r\pmod m\),预处理 \(b\cdot a^r\) 模 \(m\) 的值,枚举 \(q\),判断是否能找到对应的 \(r\)。
//Luogu P3846 #include<bits/stdc++.h> #define int long long using namespace std; map<int,int>mp; int a,b,p,ans; int BSGS(int a,int b,int p){ a%=p,b%=p,mp.clear(); if(b==1||p==1) return 0; int t=ceil(sqrt(p)),x=b,pw=1; for(int i=0;i<t;i++) mp[x]=i,x=x*a%p,pw=pw*a%p; //存入 b*(a^r) x=1; for(int i=1;i<=t;i++){ //pw=(a^t), x=(a^t)^i x=x*pw%p; if(mp.count(x)) return i*t-mp[x]; //听说写 mp.count(x) 会比 mp[x] 快 } return -1; } signed main(){ scanf("%lld%lld%lld",&p,&a,&b); ans=BSGS(a,b,p); if(~ans) printf("%lld\n",ans); else puts("no solution"); return 0; }
二、exBSGS
求解关于 \(x\) 的方程 \(a^x\equiv b\pmod m\)。\(m\) 可取任意数。
BSGS:\(\gcd(a,m)=1\),\(a\) 在模 \(m\) 意义下存在逆元,可令等式右侧出现 \(a\) 的幂次。
扩展 BSGS:考虑 \(\gcd(a,m)\neq 1\) 的情况。一个想法是,将它转化为 \(\gcd(a,m)=1\)。
设 \(d=\gcd(a,m)\)。\(a^x\equiv b\pmod m\Leftrightarrow a^x+km=b\),根据裴蜀定理,方程有解当且仅当 \(\gcd(a,m)\mid b\)。所以,若 \(d\nmid b\),则原方程无解。
否则,令方程两边同时除以 \(d\),得:
\(\displaystyle\frac{a}{d}\cdot a^{x-1}+k\cdot\frac{m}{d}=\frac{b}{d}\)
此时,若 \(\gcd(a,m)\neq 1\),则令 \(x'=x-1,m'=\frac{m}{d},b'=\frac{b}{d}\),重复上述步骤,于是可以一直做下去,直到 \(\gcd(a,m')=1\)。
不妨设重复了 \(g\) 次,每次求得的 \(d=\gcd(a,m')\) 分别为 \(d_1,d_2,\cdots,d_g\)。记 \(\prod_{i=1}^g d_i =D\),原式就是:
\(\displaystyle\frac{a^g}{D}\cdot a^{x-g}\equiv \frac{b}{D}\pmod {\frac{m}{D}}\)
那么就令 \(a'=a,b'=\frac{b}{D},m'=\frac{m}{D}\),跑一遍 BSGS 即可(在枚举 \(a^{qt}\) 判断时乘上 \(\frac{a^g}{D}\),对应代码中的 ad。最后答案加上 \(g\) 就行了)。
//Luogu P4195 #include<bits/stdc++.h> #define int long long using namespace std; int a,b,p,ans; map<int,int>mp; int BSGS(int a,int b,int p,int ad){ a%=p,b%=p,mp.clear(); //这里不写 b=1 的特判,因为左边乘了 ad int t=ceil(sqrt(p)),x=b,pw=1; for(int i=0;i<t;i++) mp[x]=i,x=x*a%p,pw=pw*a%p; x=ad; //x=1 改为 x=ad for(int i=1;i<=t;i++){ x=x*pw%p; if(mp.count(x)) return i*t-mp[x]; //这里写 mp[x] 会 TLE,听说是会新建一个 mp[x] } return -1; } int exBSGS(int a,int b,int p){ a%=p,b%=p; if(b==1||p==1) return 0; int g=0,ad=1,d,ans; while((d=__gcd(a,p))!=1){ if(b%d) return -1; g++,b/=d,p/=d,ad=(ad*a/d)%p; if(ad==b) return g; } if(~(ans=BSGS(a,b,p,ad))) return ans+g; return -1; } signed main(){ while(~scanf("%lld%lld%lld",&a,&p,&b)){ if(!a&&!b&&!p) break; ans=exBSGS(a,b,p); if(~ans) printf("%lld\n",ans); else puts("No Solution"); } return 0; }
建议把 map 改成 Hash 表 QAQ。