离散对数(Discrete Logarithm)问题是这样一个问题,它是要求解模方程
ax≡b(modm)ax≡b(modm)
这个问题是否存在多项式算法目前还是未知的,这篇文章先从 mm 是质数开始介绍大步小步法(Baby Step Giant Step)来解决它,之后再将其应用到 mm 是任意数的情况。这个算法可以在 O(m−−√)O(m) 的时间内计算出最小的 xx,或者说明不存在这样一个 xx
题目链接:BZOJ-2480、SPOJ-MOD、BZOJ-3239
首先解决 m=pm=p 是质数的情况,我们可以设 x=A⌈p–√⌉+Bx=A⌈p⌉+B,其中 0≤B<⌈p–√⌉0≤B<⌈p⌉, 0≤A<⌈p–√⌉0≤A<⌈p⌉,这样的话就变成求解 AA 和 BB 了,方程也变成
aA⌈p√⌉+B≡b(modp)aA⌈p⌉+B≡b(modp)
可以在两边同时乘以 aBaB 的逆元,由于 pp 是质数,这个逆元一定存在,于是方程变成
aA⌈p√⌉≡b⋅a−B(modp)aA⌈p⌉≡b⋅a−B(modp)
由于 A,BA,B 都是 O(p–√)O(p) 级别的数,可以先计算出右边这部分的值,存入 Hash 表,然后计算左边的值,在 Hash 表中查找,只要按照从小到大的顺序如果有解就能够找到最小的解,由于两边都只有 O(p–√)O(p) 个数,因此时间复杂度是 O(p–√)O(p) 的,这样 mm 是质数的情况就解决了
一个优化:我们可以设 x=A⌈p–√⌉−Bx=A⌈p⌉−B,其中 0≤B<⌈p–√⌉0≤B<⌈p⌉, 0<A≤⌈p–√⌉+10<A≤⌈p⌉+1,这样的话化简后的方程就是
aA⌈p√⌉≡b⋅aB(modp)aA⌈p⌉≡b⋅aB(modp)
就可以不用求出逆元,要注意只是不用求出逆元,而不是没有用到逆元的存在
现在来看 mm 不是质数的情况,同样可以设 x=A⌈m−−√⌉+Bx=A⌈m⌉+B,根据上面的推导,会发现需要用到的性质就是 aBaB 的逆元存在,所以当 mm 和 aa 互质的时候这个方法仍然有效!
如果 (m,a)≠1(m,a)≠1 该怎么办呢?我们要想办法把方程转化为 (m,a)=1(m,a)=1 的情况
把要求的模方程写成另外一种形式
ax+km=b,k∈Zax+km=b,k∈Z
设 g=(a,m)g=(a,m),这样的话可以确定如果 g∤bg∤b 那么该方程一定无解,所以当 g∣bg∣b 的时候,在方程左右两边同时除以 gg
agax−1+kmg=bg,k∈Zagax−1+kmg=bg,k∈Z
这样便消去了一个因子,得到方程
agax−1≡bg(modmg)agax−1≡bg(modmg)
令 m′=mg,b′=bg(ag)−1m′=mg,b′=bg(ag)−1(这里不可以把 gg 消掉),就可以得到新的方程
ax′≡b′(modm′)ax′≡b′(modm′)
得到解之后原方程的解 x=x′+1x=x′+1,不断重复这个过程最后一定会得到一个可以解的方程,套用刚刚的大步小步法解出后即可。要注意的是在这个过程中如果某一步发现 b′=1b′=1,那么就可以直接退出,因为这时候已经得到了解
NOTE:上面这个过程是可能执行多次的比如说 (a,m)=(6,16)→(6,8)→(6,4)→(6,2)(a,m)=(6,16)→(6,8)→(6,4)→(6,2)
下面的是代码,题目是文章开头给的链接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/* BZOJ-2480: Spoj3105 Mod
* 扩展大步小步 */
#include <cstdio>
#include <cmath>
#include <map>
intmod_pow(longlongx,longlongp,longlongmod_v)
{
longlongv=1;
while(p)
{
if(p&1)v=x*v%mod_v;
x=x*x%mod_v;
p>>=1;
}
returnv;
}
intgcd(inta,intb)
{
returnb?gcd(b,a%b):a;
}
intbaby_step_giant_step(inta,intb,intp)
{
a%=p,b%=p;
if(b==1)return0;
intcnt=0;
longlongt=1;
for(intg=gcd(a,p);g!=1;g=gcd(a,p))
{
if(b%g)return-1;
p/=g,b/=g,t=t*a/g%p;
++cnt;
if(b==t)returncnt;
}
std::map<int,int>hash;
intm=int(sqrt(1.0*p)+1);
longlongbase=b;
for(inti=0;i!=m;++i)
{
hash[base]=i;
base=base*a%p;
}
base=mod_pow(a,m,p);
longlongnow=t;
for(inti=1;i<=m+1;++i)
{
now=now*base%p;
if(hash.count(now))
returni*m-hash[now]+cnt;
}
return-1;
}
intmain()
{
inta,b,p;
while(std::scanf("%d %d %d",&a,&p,&b),p)
{
intans=baby_step_giant_step(a,b,p);
if(ans==-1)std::puts("No Solution");
elsestd::printf("%d\n",ans);
}
return0;
}