离散对数求解、BSGS算法

本文详细介绍了广义离散对数问题,包括其与椭圆曲线上的运算关系,以及如何将其规约为狭义离散对数问题。重点讨论了BSGS算法的原理和性能优化,通过调整离散对数的上限来加速计算,并提供了算法的代码实现。此外,文章还给出了两个在线判题实例,展示了算法在实际问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一,广义离散对数问题

二,广义离散对数问题的规约

三,BSGS算法

四,BSGS算法性能加速

五,OJ实战

LibreOJ 6542 离散对数

POJ 2417 Discrete Logging


一,广义离散对数问题

加解密_nameofcsdn的博客-优快云博客

包括狭义的离散对数问题,和椭圆曲线上的点的数乘的逆运算,都算广义离散对数问题,他们的求解方案都差不多。

为了能够用统一的语言来描述,我们首先考虑,如何把广义离散对数问题,规约成狭义离散对数问题。

二,广义离散对数问题的规约

1,逆元

有限域F_p中,元素的逆元可以用费马小定理+快速幂,a^(p-2)是a的逆元

椭圆曲线上,点A(x,y)的逆元是-A,即(x,-y)

2,基点的阶

有限域F_p中,元素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使得a^x\equiv b(mod\, p), 其中p是素数,0<=x<=p-1,x的上限是a的,如果a是原根,那么阶就是p-1

取参数m,0<m<p,设x=im+j,0<=j<m,0<=i<=p/m

方程化为a^{im+j}\equiv b(mod\, p)

c\equiv a^{-m}(mod\, p),即c\equiv a^{p-1-m}(mod\, p)

则方程化为a^{j}\equiv bc^i(mod\, p)

于是,问题化成寻找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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值