题意:求最小的x使得A^X = B (mod C)
题解:
作者 AekdyCoin !
【普通Baby Step Giant Step】 【问题模型】 求解 A^x = B (mod C) 中 0 <= x < C 的解,C 为素数 【思路】 我们可以做一个等价 x = i * m + j ( 0 <= i < m, 0 <=j < m) m = Ceil ( sqrt( C) ) 而这么分解的目的无非是为了转化为: (A^i)^m * A^j = B ( mod C) 之后做少许暴力的工作就可以解决问题: (1) for i = 0 -> m, 插入Hash (i, A^i mod C) (2) 枚举 i ,对于每一个枚举到的i,令 AA = (A^m)^i mod C 我们有 AA * A^j = B (mod C) 显然AA,B,C均已知,而由于C为素数,那么 (AA,C)无条件为1 于是对于这个模方程解的个数唯一(可以利用扩展欧几里得或 欧拉定理来求解) 那么对于得到的唯一解X,在Hash表中寻找,如果找到,则返回 i * m + j 注意: 由于i从小到大的枚举,而Hash表中存在的j必然是对于某个剩余系内的元素X 是最小的(就是指标) 所以显然此时就可以得到最小解 如果需要得到 x > 0的解,那么只需要在上面的步骤中判断 当 i * m + j > 0 的时候才返回 到目前为止,以上的算法都不存在争议,大家实现的代码均相差不大。可见当C为素数的时候,此类离散对数的问题可以变得十分容易实现。 【扩展Baby Step Giant Step】 【问题模型】 求解 A^x = B (mod C) 中 0 <= x < C 的解,C 无限制(当然大小有限制……) 【写在前面】 这个问题比较麻烦,目前网络上流传许多版本的做法,不过 大部分已近被证明是完全错误的! 这里就不再累述这些做法,下面是我的做法( 有问题欢迎提出) 下面先给出算法框架,稍后给出详细证明: (0) for i = 0 -> 50 if(A^i mod C == B) return i O(50) (1) d<- 0 D<- 1 mod C while((tmp=gcd(A,C))!=1) { if(B%tmp)return -1; // 无解! ++d; C/=tmp; B/=tmp; D=D*A/tmp%C; } (2) m = Ceil ( sqrt(C) ) //Ceil是必要的 O(1) (3) for i = 0 -> m 插入Hash表(i, A^i mod C) O( m) (4) K=pow_mod(A,m,C) for i = 0 -> m 解 D * X = B (mod C) 的 唯一解 (如果存在解,必然唯一!) 之后Hash表中查询,若查到(假设是 j),则 return i * m + j + d 否则 D=D*K%C,继续循环 (5) 无条件返回 -1 ;//无解! 下面是证明: 推论1: A^x = B(mod C) 等价为 A^a * A^b = B ( mod C) (a+b) == x a,b >= 0 证明: A^x = K * C + B (模的定义) A^a * A^b = K*C + B( a,b >=0, a + b == x) 所以有 A^a * A^b = B(mod C) 推论 2: 令 AA * A^b = B(mod C) 那么解存在的必要条件为: 可以得到至少一个可行解 A^b = X (mod C) 使上式成立 推论3 AA * A^b = B(mod C) 中解的个数为 (AA,C) 由推论3 不难想到对原始Baby Step Giant Step的改进 For I = 0 -> m For any solution that AA * X = B (mod C) If find X Return I * m + j 而根据推论3,以上算法的复杂度实际在 (AA,C)很大的时候会退化到几乎O(C) 归结原因,是因为(AA,C)过大,而就是(A,C)过大 |
#include<cstdio> #include<cstring> #include<cmath> using namespace std; #define lint __int64 #define MAXN 131071 struct HashNode { lint data, id, next; }; HashNode hash[MAXN<<1]; bool flag[MAXN<<1]; lint top; void Insert ( lint a, lint b ) { lint k = b & MAXN; if ( flag[k] == false ) { flag[k] = true; hash[k].next = -1; hash[k].id = a; hash[k].data = b; return; } while( hash[k].next != -1 ) { if( hash[k].data == b ) return; k = hash[k].next; } if ( hash[k].data == b ) return; hash[k].next = ++top; hash[top].next = -1; hash[top].id = a; hash[top].data = b; } lint Find ( lint b ) { lint k = b & MAXN; if( flag[k] == false ) return -1; while ( k != -1 ) { if( hash[k].data == b ) return hash[k].id; k = hash[k].next; } return -1; } lint gcd ( lint a, lint b ) { return b ? gcd ( b, a % b ) : a; } lint ext_gcd (lint a, lint b, lint& x, lint& y ) { lint t, ret; if ( b == 0 ) { x = 1, y = 0; return a; } ret = ext_gcd ( b, a % b, x, y ); t = x, x = y, y = t - a / b * y; return ret; } lint mod_exp ( lint a, lint b, lint n ) { lint ret = 1; a = a % n; while ( b >= 1 ) { if( b & 1 ) ret = ret * a % n; a = a * a % n; b >>= 1; } return ret; } lint BabyStep_GiantStep ( lint A, lint B, lint C ) { top = MAXN; B %= C; lint tmp = 1, i; for ( i = 0; i <= 100; tmp = tmp * A % C, i++ ) if ( tmp == B % C ) return i; lint D = 1, cnt = 0; while( (tmp = gcd(A,C)) !=1 ) { if( B % tmp ) return -1; C /= tmp; B /= tmp; D = D * A / tmp % C; cnt++; } lint M = (lint)ceil(sqrt(C+0.0)); for ( tmp = 1, i = 0; i <= M; tmp = tmp * A % C, i++ ) Insert ( i, tmp ); lint x, y, K = mod_exp( A, M, C ); for ( i = 0; i <= M; i++ ) { ext_gcd ( D, C, x, y ); // D * X = 1 ( mod C ) tmp = ((B * x) % C + C) % C; if( (y = Find(tmp)) != -1 ) return i * M + y + cnt; D = D * K % C; } return -1; } int main() { lint A, B, C; while( scanf("%I64d%I64d%I64d",&A,&C,&B ) !=EOF ) { if ( !A && !B && !C ) break; memset(flag,0,sizeof(flag)); lint tmp = BabyStep_GiantStep ( A, B, C ); if ( tmp == -1 )puts("No Solution"); else printf("%I64d\n",tmp); } return 0; }