generalized baby-step giant-step algorithm

一份很好的说明见这里:
http://viva-villa.org/2010/02/baby-step-giant-step.html
用这种方法实现的代码:
void exgcd(int m, int n, int &d, int &x, int &y)
{
	if (n == 0) {
		d = m, x = 1, y = 0;
	} else {
		exgcd(n, m % n, d, y, x);
		y -= x * (m / n);
	}
}

LL mod(LL x, LL n)
{ return (x % n + n) % n; }

int power(int x, int n, int m)
{
	int r = 1;
	while (n) {
		if (n & 1) r = (LL)r * x % m;
		n >>= 1;
		x = (LL)x * x % m;
	}
	return r;
}

// a ^ x = b (mod n)
map<int, int> hash;
int baby_giant(int a, int b, int n)
{
	int m = (int)ceil(sqrt((double)n));
	// make: a ^ j hash table 
	hash.clear();
	int aj = 1;
	hash[aj] = 0;
	for (int j = 1; j < m; ++j)
	{
		aj = (LL)aj * a % n;
		if (!hash.count(aj)) hash[aj] = j;
	}
	// enum a ^ mi
	int ami = 1, am = power(a, m, n);
	for (int i = 0; i < m; ++i)
	{
		// solve eqn(gcd(a, n) == 1): a ^ mi * x = b (mod n)
		int d, x0, y0, x;
		exgcd(ami, n, d, x0, y0);
		if (b % d != 0) continue;
		x = mod(x0 * b / d, n);
		for (int k = 0; k < d; ++k)
		{
			if (hash.count(x))
				return m * i + hash[x];
			x = mod(x + n / d, n);
		}
		ami = (LL)ami * am % n;
	}
	return -1;
}

遗憾的是每次解线性同余方程组并枚举其解的效率不高,下面介绍更高效的方法
(参考了http://hi.baidu.com/aekdycoin/blog/item/b317ca18bb24334942a9ad55.

求解 a ^ x = b (mod n),当 a 和 n 不互素时该怎么办呢?
我们知道对于模运算中的除法有这样的定理:a / t = b / t (mod n / gcd(n, t))
那么我们可以用这个定理将 n 与 a ^ x 不互素的因子消除掉

可以想象当x足够大时,可以用定理将 n 与 a ^ x 不互素的因子全部消除掉,设此时的 x 为 f

写得更形式化一点就是:
设 f 满足 gcd(n / gcd(a ^ f, n), a) = 1
也就是 a ^ f (mod n) 时,刚好可以用上述定理把 a ^ f 和 n 中的因子都消掉
则原方程变成 a0 * a ^ x = b0 (mod n0),且 a 和 n0 互素的形式
其中 a0 = a ^ f / gcd(a ^ f, n), b0 = b / gcd(a ^ f, n), n0 = n / gcd(a ^ f, n)

此时就是分情况讨论了,设原方程的解为 x0,则要么 x0 <= f,要么 x0 > f
若 x0 <= f,那么在上述消因子的过程中就可以得到 x0
若 x0 > f,那么对消掉因子后的方程 a0 * a ^ x = b0 (mod n0) 做 baby-step-giant-step 就行了

再说说接下来 baby-step-giant-step 的过程:
首先我们要解决的问题是 a0 * a ^ x = b0 (mod n0)
写成a0 * a ^ (m * i + j) = b0 (mod n0) 的形式
其中m虽然可以随意取,不过取 m = (int)ceil(sqrt((double)n0)) 效率最高
再变成(a0 * a ^ mi) * (a ^ j) = b0 (mod n0)的形式
可以看到左边有两部分,前一部分都有 n / m 个值,后一部分有 m 个值,因为我们取 m = sqrt(n),所以这两部分的复杂度都是sqrt(n)
我们可以先预处理其中一部分存到 hash 表里,之后枚举另一部分反过来推算应该对应hash表中的哪个值,这就是baby-step-giant-step最核心的思想

更进一步 a ^ j = b0 * inv(a0 * a ^ mi) mod(n0)
inv表示在群 n0 中的逆元

// hdu2815_3.cpp
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#define out(v) cerr << #v << ": " << (v) << endl
#define SZ(v) ((int)(v).size())
using namespace std;
typedef long long LL;

int gcd(int m, int n)
{ return n == 0 ? m : gcd(n, m % n); }

void exgcd(int m, int n, int &d, int &x, int &y)
{
	if (n == 0) {
		d = m, x = 1, y = 0;
	} else {
		exgcd(n, m % n, d, y, x);
		y -= x * (m / n);
	}
}

LL mod(LL x, LL n)
{ return (x % n + n) % n; }

int power(int x, int n, int m)
{
	int r = 1 % m;
	x %= m;
	while (n) {
		if (n & 1) r = (LL)r * x % m;
		n >>= 1;
		x = (LL)x * x % m;
	}
	return r;
}

// a ^ x = b (mod n), 0 <= a, 0 <= b < n
map<int, int> hash;
int baby_giant(int a, int b, int n)
{
	// af
	int a0 = 1 % n, b0 = b, n0 = n, f = 0, g;
	while ((g = gcd(a, n0)) > 1)
	{
		if (a0 == b0) return f;
		if (b0 % g != 0) return -1;
		n0 /= g;
		b0 /= g;
		a0 = (LL)a0 * a / g % n0;
		++f;
	}
	if (a0 == b0) return f;
	
	// require gcd(a0, n) == 1
	int m = (int)ceil(sqrt((double)n0));
	// make: a ^ j hash table 
	hash.clear();
	int aj = 1 % n0;
	hash[aj] = 0;
	for (int j = 1; j < m; ++j)
	{
		aj = (LL)aj * a % n0;
		if (!hash.count(aj)) hash[aj] = j;
	}
	// enum a ^ mi
	int aa = a0, am = power(a, m, n0);
	for (LL i = 0; i < m; ++i)
	{
		int d, x, y;
		exgcd(aa, n0, d, x, y);
		// assert(d == 1);
		int bb = mod((LL)b0 * x, n0);
		if (hash.count(bb)) return f + m * i + hash[bb];
		aa = (LL)aa * am % n0;
	}
	return -1;
}

int main()
{
	int K, P, N;
	while (scanf("%d%d%d", &K, &P, &N) != EOF)
	{
		if (N >= P)
		{
			printf("Orz,I can’t find D!\n");
			continue;
		}
		LL D = baby_giant(K, N, P);
		if (D == -1)
			printf("Orz,I can’t find D!\n");
		else
			printf("%d\n", D);
	}
	return 0;
}

你可能感兴趣的:(F#,Blog,J#)