离散对数(Discrete Logarithm)问题是这样一个问题,它是要求解模方程
ax≡b(modm)
ax≡b(modm)
这个问题是否存在多项式算法目前还是未知的,这篇文章先从
m
m 是质数开始介绍大步小步法(Baby Step Giant Step)来解决它,之后再将其应用到
m
m 是任意数的情况。这个算法可以在
O(m−−√)
O(m) 的时间内计算出最小的
x
x,或者说明不存在这样一个
x
x
题目链接:BZOJ-2480、SPOJ-MOD、BZOJ-3239
首先解决
m=p
m=p 是质数的情况,我们可以设
x=A⌈p–√⌉+B
x=A⌈p⌉+B,其中
0≤B<⌈p–√⌉
0≤B<⌈p⌉,
0≤A<⌈p–√⌉
0≤A<⌈p⌉,这样的话就变成求解
A
A 和
B
B 了,方程也变成
aA⌈p√⌉+B≡b(modp)
aA⌈p⌉+B≡b(modp)
可以在两边同时乘以
aB
aB 的逆元,由于
p
p 是质数,这个逆元一定存在,于是方程变成
aA⌈p√⌉≡b⋅a−B(modp)
aA⌈p⌉≡b⋅a−B(modp)
由于
A,B
A,B 都是
O(p–√)
O(p) 级别的数,可以先计算出右边这部分的值,存入 Hash 表,然后计算左边的值,在 Hash 表中查找,只要按照从小到大的顺序如果有解就能够找到最小的解,由于两边都只有
O(p–√)
O(p) 个数,因此时间复杂度是
O(p–√)
O(p) 的,这样
m
m 是质数的情况就解决了
一个优化:我们可以设
x=A⌈p–√⌉−B
x=A⌈p⌉−B,其中
0≤B<⌈p–√⌉
0≤B<⌈p⌉,
0<A≤⌈p–√⌉+1
0<A≤⌈p⌉+1,这样的话化简后的方程就是
aA⌈p√⌉≡b⋅aB(modp)
aA⌈p⌉≡b⋅aB(modp)
就可以不用求出逆元,要注意只是不用求出逆元,而不是没有用到逆元的存在
现在来看
m
m 不是质数的情况,同样可以设
x=A⌈m−−√⌉+B
x=A⌈m⌉+B,根据上面的推导,会发现需要用到的性质就是
aB
aB 的逆元存在,所以当
m
m 和
a
a 互质的时候这个方法仍然有效!
如果
(m,a)≠1
(m,a)≠1 该怎么办呢?我们要想办法把方程转化为
(m,a)=1
(m,a)=1 的情况
把要求的模方程写成另外一种形式
ax+km=b,k∈Z
ax+km=b,k∈Z
设
g=(a,m)
g=(a,m),这样的话可以确定如果
g∤b
g∤b 那么该方程一定无解,所以当
g∣b
g∣b 的时候,在方程左右两边同时除以
g
g
agax−1+kmg=bg,k∈Z
agax−1+kmg=bg,k∈Z
这样便消去了一个因子,得到方程
agax−1≡bg(modmg)
agax−1≡bg(modmg)
令
m′=mg,b′=bg(ag)−1
m′=mg,b′=bg(ag)−1(这里不可以把
g
g 消掉),就可以得到新的方程
ax′≡b′(modm′)
ax′≡b′(modm′)
得到解之后原方程的解
x=x′+1
x=x′+1,不断重复这个过程最后一定会得到一个可以解的方程,套用刚刚的大步小步法解出后即可。要注意的是在这个过程中如果某一步发现
b′=1
b′=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>
int
mod_pow
(
long
long
x
,
long
long
p
,
long
long
mod_v
)
{
long
long
v
=
1
;
while
(
p
)
{
if
(
p
&
1
)
v
=
x
*
v
%
mod_v
;
x
=
x
*
x
%
mod_v
;
p
>>
=
1
;
}
return
v
;
}
int
gcd
(
int
a
,
int
b
)
{
return
b
?
gcd
(
b
,
a
%
b
)
:
a
;
}
int
baby_step_giant_step
(
int
a
,
int
b
,
int
p
)
{
a
%=
p
,
b
%=
p
;
if
(
b
==
1
)
return
0
;
int
cnt
=
0
;
long
long
t
=
1
;
for
(
int
g
=
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
)
return
cnt
;
}
std
::
map
<
int
,
int
>
hash
;
int
m
=
int
(
sqrt
(
1.0
*
p
)
+
1
)
;
long
long
base
=
b
;
for
(
int
i
=
0
;
i
!=
m
;
++
i
)
{
hash
[
base
]
=
i
;
base
=
base
*
a
%
p
;
}
base
=
mod_pow
(
a
,
m
,
p
)
;
long
long
now
=
t
;
for
(
int
i
=
1
;
i
<=
m
+
1
;
++
i
)
{
now
=
now
*
base
%
p
;
if
(
hash
.
count
(
now
)
)
return
i
*
m
-
hash
[
now
]
+
cnt
;
}
return
-
1
;
}
int
main
(
)
{
int
a
,
b
,
p
;
while
(
std
::
scanf
(
"%d %d %d"
,
&a
,
&p
,
&b
)
,
p
)
{
int
ans
=
baby_step_giant_step
(
a
,
b
,
p
)
;
if
(
ans
==
-
1
)
std
::
puts
(
"No Solution"
)
;
else
std
::
printf
(
"%d\n"
,
ans
)
;
}
return
0
;
}
|