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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值