目录
一,广义离散对数问题
包括狭义的离散对数问题,和椭圆曲线上的点的数乘的逆运算,都算广义离散对数问题,他们的求解方案都差不多。
为了能够用统一的语言来描述,我们首先考虑,如何把广义离散对数问题,规约成狭义离散对数问题。
二,广义离散对数问题的规约
1,逆元
有限域中,元素的逆元可以用费马小定理+快速幂,a^(p-2)是a的逆元
椭圆曲线上,点A(x,y)的逆元是-A,即(x,-y)
2,基点的阶
有限域中,元素a的阶是不难求的,因为阶一定是p-1的约数
椭圆曲线上,点A的阶也是不难求的,因为阶一定是大群阶的约数
只需要用到逆元和阶的求解算法,可以直接用在椭圆曲线这种广义离散对数问题上。
三,BSGS算法
In group theory, a branch of mathematics, the baby-step giant-step is a meet-in-the-middle algorithm for computing the discrete logarithm or order of an element in a finite abelian group due to Daniel Shanks.
BSGS是baby-step giant-step的缩写,BSGS算法算法也叫Shanks算法。
求x使得, 其中p是素数,0<=x<=p-1,x的上限是a的阶,如果a是原根,那么阶就是p-1
取参数m,0<m<p,设x=im+j,0<=j<m,0<=i<=p/m
方程化为
令,即
则方程化为
于是,问题化成寻找2个数组中有没有相同的数,其中一个数组有m个数,一个数组有p/m个数
假设查询一个数组的时间可以做到O(1),那么总时间就是m+p/m
所以,一般取m=sqrt(p),shanks算法的时间复杂度是sqrt(p)
注意,两个数组中,相同的2个数可能有很多组,一定要找到最小的x(对应最小的i)才是阶
PS:表面上,shanks算法只需要用的阶的上限,不需要算出阶的值,但是对于椭圆曲线,比如X25519,基点的协因子是8,那么阶的上限就不是大群阶,而是它的1/8,据此可以让破解速度快8倍
代码实现:
class BSGS :public Multi //求离散对数a^x=b(mod p)
{
public:
static long long discreteLog(long long a, long long b, int p)
{
a = (a%p + p) % p, b = (b%p + p) % p;
if (a == 0)return (b == 0) ? 1 : -1;
long long m = long long(sqrt(double(p)));///
long long c = MultiMulti(a, p - 1 - m, p);
set<long long>s;
map<long long, long long>ma;
long long x = 1;
for (int j = 0; j < m; j++) {
if (j > 0 && x == 1)break;
s.insert(x);
ma[x] = j;
x = x * a%p;
}
for (int i = 0; i <= p / m; i++) { ///
if (s.find(b) != s.end()) {
return i * m + ma[b];
}
b = b * c%p;
}
return -1;
}
};
四,BSGS算法性能加速
我在基础代码中用/标注了2行,其中的p是同一个含义,表示离散对数的上限Max,而这正是影响BSGS算法的性能的一个关键。
首先要重新认识一下BSGS算法的性能,它的耗时有2部分,j的循环是固定耗时m,而i的循环是最多Max/m。
如果能快速给出更优的离散对数的上限,即更小的Max,那么2段循环的耗时就会减少。
实际上,GetOrder(a,p) <= GetPhi(p) <= p-1,而GetOrder(a,p)也是离散对数的上限。
为了提高整体性能,我对GetOrder函数进行了性能优化,代码在ACM模板 中。
替换p,做个性能测试。
int main()
{
clock_t s, e;
long long a, b, p;
cin >> p >> a >> b;
s = clock();
long long ans;
for (int i = 0; i < 1000; i++) {
ans = DiscreteLog(a, b, p);
}
if (ans == -1)cout << "no solution\n";
else cout << ans << endl;
e = clock();
cout << e - s;
return 0;
}
结果:
p 21.4秒
GetPhi(p) 20.0秒
GetOrder(a,p) 14.9秒
所以最高性能的版本是:
class BSGS :public Multi //求离散对数a^x=b(mod p)
{
public:
static long long discreteLog(long long a, long long b, int p)
{
a = (a%p + p) % p, b = (b%p + p) % p;
if (a == 0)return (b == 0) ? 1 : -1;
int maxLog = GetOrder(a, p);
long long m = long long(sqrt(double(maxLog)));
long long c = MultiMulti(a, p - 1 - m, p);
set<long long>s;
map<long long, long long>ma;
long long x = 1;
for (int j = 0; j < m; j++) {
if (j > 0 && x == 1)break;
s.insert(x);
ma[x] = j;
x = x * a%p;
}
for (int i = 0; i <= maxLog / m; i++) {
if (s.find(b) != s.end()) {
return i * m + ma[b];
}
b = b * c%p;
}
return -1;
}
};
五,OJ实战
LibreOJ 6542 离散对数
题目描述
给定素数 p ,T 次询问使得 a,b 满足 ax≡b(modp)的最小非负整数 x。
输入格式
第一行包括两个正整数 T 和 p。
接下来 T 行,每行包括两个正整数,表示一组询问。
输出格式
输出共 T 行,每行包括一个非负整数,表示这个最小的 x,无解输出 −1。
样例
数据范围与提示
对于 20%20% 的数据,p<10^3
对于 40%40% 的数据,p<10^8
对于 60%60% 的数据,p<10^12
对于 80%80% 的数据,p<10^18
对于 100%100% 的数据,1≤T≤200,2≤p<3×10^18且 p 为素数。
代码:
......
int main()
{
long long T,a,b,p;
cin>>T>>p;
while(T--){
cin>>a>>b;
cout<<DiscreteLog(a,b,p)<<endl;
}
return 0;
}
结果:
通过40%的用例, p的值太大了。
POJ 2417 Discrete Logging
Given a prime P, 2 <= P < 2^31, an integer B, 2 <= B < P, and an integer N, 1 <= N < P, compute the discrete logarithm of N, base B, modulo P. That is, find an integer L such that
B^L == N (mod P)
Input
Read several lines of input, each containing P,B,N separated by a space.
Output
For each line print the logarithm on a separate line. If there are several, print the smallest; if there is none, print "no solution".
Sample Input
5 2 1 5 2 2 5 2 3 5 2 4 5 3 1 5 3 2 5 3 3 5 3 4 5 4 1 5 4 2 5 4 3 5 4 4 12345701 2 1111111 1111111121 65537 1111111111
Sample Output
0 1 3 2 0 3 1 2 0 no solution no solution 1 9584351 462803587
Hint
The solution to this problem requires a well known result in number theory that is probably expected of you for Putnam but not ACM competitions. It is Fermat's theorem that states
B(P-1) == 1 (mod P)
for any prime P and some other (fairly rare) numbers known as base-B pseudoprimes. A rarer subset of the base-B pseudoprimes, known as Carmichael numbers, are pseudoprimes for every base between 2 and P-1. A corollary to Fermat's theorem is that for any m
B(-m) == B(P-1-m) (mod P) .
思路:
这题就是LibreOJ - 6542 离散对数,只不过数据量小一点,所以我卡着2秒AC了。
本来代码基本不用改,只需要改main函数即可,不过因为POJ的编译器太老了,所以把unordered set换成了set
代码:
......
int main()
{
long long a, b, p;
while (cin >> p >> a >> b) {
long long ans = DiscreteLog(a, b, p);
if (ans == -1)cout << "no solution\n";
else cout << ans << endl;
}
return 0;
}