原根与BSGS
我是鸽子。
$ \mathbf{原根}$
阶
根据欧拉定理,对于 \(n\in \mathbb N^* ,a\in \mathbb Z\) 且 \(\gcd(a,n)=1\) ,有 \(a^{\varphi (n) }\equiv 1 \pmod n\)。
此时满足 \(a^{x}\equiv 1 \pmod n\) 的最小正整数存在,\(x\) 称作 \(a\) 模 \(n\) 的阶,记作 \(\delta_{n}(a)\) 。
性质
不证了,OI-wiki 上都有。
-
\(a,a^2,\cdots,a^{\delta_n(a)}\) 模 \(n\) 不同余。
-
若 \(a^m\equiv 1 \pmod n\) ,则 \(\delta_n(a) | n\) 。
-
设 \(n\in \mathbb N^* ,a,b\in \mathbb Z\) 且 \(\gcd(a,n)=1,gcd(b,n)=1\) ,那么 \(\delta_n(ab)=\delta_n(a)\delta_n(b)\) 的充要条件是 \(gcd(\delta_n(a),\delta_n(b))=1\) 。
-
设 \(k\in \mathbb N^*, n\in \mathbb N^* ,a\in \mathbb Z\) ,则 \(\delta_n (a^k)=\frac{\delta_n (a)}{gcd(\delta_n (a),k)}\)
原根
若 \(n\in \mathbb N^* ,a\in \mathbb Z\) 且 \(\gcd(a,n)=1,\delta_n(a)=\varphi(n)\),那么称 \(a\) 为 \(n\) 的一个原根。
原根个数
若某个整数 \(n\) 有原根,则它原根的个数为 \(\varphi(\varphi(n))\) 。
原根存在定理
只有 \(2,4,p^k,2p^k\) 存在原根,其中 \(p\) 为奇质数。
原根判定定理
设 \(n \ge 3,\gcd(a,n)=1\) ,\(a\) 是 \(n\) 的一个原根当且仅当对于 \(\varphi(n)\) 的所有质因子 \(p\) 都有 \(a^{\frac{\varphi(n)}{p}} \not\equiv 1 \pmod n\) 。
\(n\) 的最小原根是不多于 \(O(n^{0.25})\) 级别的,因此可以直接从小到大枚举。
利用原根求解底数同余方程
题意:
给定 \(k,a,p\),求 \(x^k \equiv a \pmod p\) 的所有根,\(p\) 为质数,根的范围是 \([0,p-1]\) 。
\(0\le a<p\le 10^9 ,2\le k\le 10^6\)
思路:
求出 \(p\) 的原根 \(g\),设 \(x=g^t\) ,则有:
现在已知 \(g^k\) ,用 \(\operatorname{BSGS}\) 求解出 \(t\) 的所有解即可。
或者先用 \(\operatorname{BSGS}\) 求出 \(g^{m} \equiv a \pmod p\) 的最小解,再通过 \(\operatorname{exgcd}\) 求二元不定方程 $m+x\varphi(p)=kt $ 中 \(t\) 的所有解。
$ \mathbf{BSGS}$
普通BSGS
题意:
给定非负整数 \(a,b\) 以及质数 \(p\),求 \(a^x\equiv b \pmod p\) 的最小非负整数解,若无解则输出 no solution
$ a,b,p <2^{31}$
思路:
根据费马小定理,质数 \(p\) 不整除 \(a\) 的情况下存在 \(a^{p-1}\equiv 1 \pmod p\),得知解的范围应该是 \([0,p-1)\),直接枚举将会有一个感人的复杂度。
考虑设 \(t=\lceil \sqrt p \rceil\) ,将解 \(x\) 表示成 \(i\times t-j\) 的形式:
两边同时乘以 \(a^{j}\) :
提前用 map
或者 unordered_map
将右侧的值 \(b\times a^j\) 映射为 \(j\),然后枚举 \(i\) ,对于 \(a^{i\times t }\) 在 map
中查找是否存在 \(b\times a_j\) 与其同余,若没有则跳过,若有则答案即为 $i\times t-j $ ,一直到最后输出无解。
当 \(j\) 从小到大映射时,相同值所代表的 \(j\) 一定是更大的,于是 \(i\times t-j\) 一定会更小,符合最小非负整数解的条件。
映射的范围和枚举的上界都是 \(\lceil \sqrt p \rceil\) ,因此时间复杂度为 \(O(\sqrt p)\)。
特殊注意:\(a \equiv 0 \pmod p\) 时当且仅当 \(b \equiv 0 \pmod p\) 时有解( \(x=1\) ),否则无解。
code
#include <map>
#include <cmath>
#include <cstdio>
#include <algorithm>
#define int long long
#define Reg register
using namespace std;
int b,p,n;
map<int,int> vis;
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<1)+(s<<3)+(ch^48);
ch=getchar();
}
return s*w;
}
inline int qpow(int A,int B){
int Ans=1;
while(B){
if(B&1) Ans=Ans*A%p;
A=A*A%p;
B>>=1;
}
return Ans;
}
inline int BSGS(){
n%=p;
int t=sqrt(p)+1;
for(Reg int i=0;i<t;++i) vis[n]=i,n=n*b%p;
int v=qpow(b,t),f=1;
if(!v){
if(!n) printf("0\n");
else printf("no solution\n");
exit(0);
}
for(Reg int i=0;i<=t;++i){
int j=(vis.find(f)==vis.end())?-1:vis[f];
if(j>=0&&i*t-j>=0) return i*t-j;
f=f*v%p;
}
printf("no solution\n");
exit(0);
return 0;
}
signed main(){
p=read(),b=read(),n=read();
printf("%lld\n",BSGS());
return 0;
}
实际上以上流程可以适用于 \(\gcd(a,p)=1\) 的情况。
但如果 \(\gcd(a,p)\ne 1\) 呢?
拓展BSGS
题意:
给定正整数 \(a,b,p\),求 \(a^x\equiv b \pmod p\) 的最小非负整数解,若无解则输出 no solution
多组询问,保证 \(\sum \sqrt p \le 5\times 10^6\)
$ 1\le a,b,p \le 10^9$
思路:
发现 \(a,p\) 不一定互质了(悲
容易知道普通 \(\operatorname{BSGS}\) 能够得出正解得原因是存在 \(a^j\) 模 \(p\) 的逆元,而现在这个条件已经不被满足。
考虑将题意转化成 \(\gcd(a,p)=1\) 的情况。假设我们已经求出来了 \(x\) :
上式可以转化成二元不定方程的形式:
拆一个 \(a\) 出来:
设 \(d=gcd(a,p)\),根据翡蜀定理,只有 \(d| b\) 时方程才有解,于是我们直接给两边除以 \(d\)。
这么处理下去,直到 \(gcd(a,b)=1\) :
设 \(g=\frac{a^c}{\prod_{i=1}^c d_i},p'=\frac{p}{\prod_{i=1}^c d_i},b'=\frac{b}{\prod_{i=1}^c d_i}\),此时我们应该求以下同余方程的解:
因为 \(gcd(a,p')=1\) ,所以能使用普通 \(\operatorname{BSGS}\) 求解,最后的答案为 \(i\times t-j+c\) 。
由于在 \(gcd(a,p)\ne 1\) 时, \(gcd(a,p)\ge 2\) ,可知 \(c\) 是 \(O(\log p)\) 级别的,单次询问的复杂度为 \(O(\sqrt p+\log p)\) 。
细节比较多。
code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <unordered_map>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;
using namespace std;
#define Reg register
#define ll long long
const int maxn=5100000;
int a,b,mod;
gp_hash_table<ll,ll> vis;
//unorder_map被卡了(悲,不想手打哈希表的可以写这个
inline ll read(){
ll s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<1)+(s<<3)+(ch^48);
ch=getchar();
}
return s*w;
}
inline ll qpow(ll A,ll B,ll p){
ll Ans=1;
while(B){
if(B&1) Ans=Ans*A%p;
A=A*A%p;
B>>=1;
}
return Ans;
}
inline ll gcd(ll A,ll B){
return B?gcd(B,A%B):A;
}
inline void Nosolu(){
printf("No Solution\n");
}
inline void EXBSGS(){
a%=mod,b%=mod;
if(b==1||mod==1) return printf("0\n"),void();
if(!a){
if(b) return Nosolu(),void();
return printf("1\n"),void();
}
int c=0,p=mod,g=1;
for(Reg int d=gcd(a,p);d!=1;d=gcd(a,p)){
if(b%d) return Nosolu(),void();
++c,p/=d,b/=d,g=1ll*g*(a/d)%p;
if(b==g) return printf("%d\n",c),void();
}
vis.clear();
if(p==1) return printf("%d\n",c),void();
int t=sqrt(p)+1;
for(Reg int i=0;i<t;++i) vis[b]=i,b=1ll*b*a%p;
int At=qpow(a,t,p),f=g;
for(Reg int i=1;i<=t;++i){
f=1ll*f*At%p;
ll j=(vis.find(f)==vis.end())?-1:vis[f];
if(j>=0&&i*t-j>=0) return printf("%lld\n",i*t-j+c),void();
}
Nosolu();
}
int main(){
a=read(),mod=read(),b=read();
while(!(a==0&&b==0&&mod==0)){
EXBSGS();
a=read(),mod=read(),b=read();
}
return 0;
}