前言:在前两篇博客,我提到了解决单个线性同余方程的方法,以及解决线性同余方程组的方法,但是,当单个同余方程变成
或
时,蒟蒻的我又得自闭了,为了不再自闭,就不得不提到BSGS算法 。BSGS算法,原名Baby Step,Giant Step算法,不过,有人称之为北上广深算法,或者拔山盖世算法。经过本蒟蒻的鉴定,这个算法需要用到一些欧拉定理的知识,所以,我们不妨先跑一下题#手动滑稽# 。
本文涉及到的欧拉定理:当
与
互质时,有
BSGS算法
求解
,其中
与
互质。
如果
有解,且
为所有解的最小正整数解,则
。
根据欧拉定理,
,且
,不难看出指数从
到
为一个循环节(不一定最小哦(⊙o⊙))。
而
的最大值不超过
,所以在
到
一定可以找到一个可行的解。
我们设
,其中
(向下取整可能够不到
),
,
。
则方程变为
变为
,对于所有的
,把
插入一个HASH表(可用map也可用邻接表)。
然后枚举
的所有可能取值,计算出
,在HASH表中查找是否存在对应的
,更新答案即可。时间复杂度为
还有特判就是如果
且
解为1,若
且
不等于0,无解。若
等于1,直接返回零(不知为何少了这个特判会错)。
板子http://poj.org/problem?id=2417
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#define ll long long
using namespace std;
inline int power( int a , int b , int p ) {
int res = 1;
a %= p;
while (b) {
if ( b & 1 ) {
res = ( (ll)res * (ll)a ) % p;
}
a = ( (ll)a * (ll)a ) % p;
b >>= 1;
}
return res;
}
inline int bsgs( int a , int b , int p ) {//若无解则返回-1
if ( b == 1 ) {
return 0;//特判
}
if ( !a ) {
return b == 0 ? 1 : -1;//特判
}
map< int , int > hash;
hash.clear();
b %= p;
int t = (int)(sqrt(p * 1.0)) + 1;
for ( int j = 0 ; j <= t - 1 ; ++j ) {
int val = ( (ll)b * (ll)power( a , j , p ) ) % p;
hash[val] = j;
}
a = power( a , t , p );
for ( int i = 0 ; i <= t ; ++i ) {
int val = power( a , i , p );
if ( hash.find(val) == hash.end() ) {
continue;
}
int temp = hash[val];
if ( i * t - temp >= 0 ) {
return i * t - temp;
}
}
return -1;
}
int main () {
int a , b , p;
while ( scanf("%d%d%d",&p,&a,&b) != EOF ) {
int tem = bsgs( a , b , p );
if ( tem == -1 ) {
printf("no solution\n");
} else {
printf("%d\n",tem);
}
}
return 0;
}
map在poj上会T飞,考虑邻接表+真正的hash。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define ll long long
#define mod 100007
using namespace std;
struct hashtable{
int bcnt;
int head[mod];
struct line{
int next;
int to;
int val;
}str[10 * mod];
inline void insert( int from , int to , int val ) {
str[++bcnt].next = head[from];
head[from] = bcnt;
str[bcnt].to = to;
str[bcnt].val = val;
return;
}
inline void clear() {
bcnt = 0;
memset( head , 0 , sizeof(head) );
}
inline void doit( int x , int y ) {
int te = x % mod;
insert( te , y , x );
}
inline int query( int x ) {
int te = x % mod;
for ( int i = head[te] ; i ; i = str[i].next ) {
int sn = str[i].to;
int va = str[i].val;
if ( va == x ) {
return sn;
}
}
return -1;
}
}hash;
inline int power( int a , int b , int p ) {
int res = 1;
a %= p;
while (b) {
if ( b & 1 ) {
res = ( (ll)res * (ll)a ) % p;
}
a = ( (ll)a * (ll)a ) % p;
b >>= 1;
}
return res;
}
inline int bsgs( int a , int b , int p ) {//若无解则返回-1
if ( b == 1 ) {
return 0;//特判
}
if ( !a ) {
return b == 0 ? 1 : -1;//特判
}
hash.clear();
b %= p;
int t = (int)(sqrt(p * 1.0)) + 1;
for ( int j = 0 ; j <= t - 1 ; ++j ) {
int val = ( (ll)b * (ll)power( a , j , p ) ) % p;
hash.doit( val , j );
}
a = power( a , t , p );
for ( int i = 0 ; i <= t ; ++i ) {
int val = power( a , i , p );
int temp = hash.query(val);
if ( temp == -1 ) {
continue;
}
if ( i * t - temp >= 0 ) {
return i * t - temp;
}
}
return -1;
}
int main () {
int a , b , p;
while ( scanf("%d%d%d",&p,&a,&b) != EOF ) {
int tem = bsgs( a , b , p );
if ( tem == -1 ) {
printf("no solution\n");
} else {
printf("%d\n",tem);
}
}
return 0;
}
扩展BSGS算法
还是求解
,其中
与
不互质。
大致做法跟BSGS算法差不多,只是多了一个消除因子的操作,不难想到:
设
,
,
。
对于原式
根据同余的一条性质,原式等价于
因而,开始时我们不断消除
,
的公因子,为了方便,采取将
,
不断除以公因子,对于
记录被消去公因子的
的个数,如果
不能整除这些公因子(其实还有一些特判),那么问题无解。
其实上述操作等价于消除
,
的公因子,如果觉得我的表述晦涩难懂,可以参考以下代码。
我以后的表述会用上以下代码中的变量。
ll D = 1 , cnt = 0;
while ( gcd( A , P ) != 1 ) {
if ( B % gcd( A , P ) != 0 ) {
printf("No Solution");
return 0;
}
B /= gcd( A , C );
P /= gcd( A , C );
D *= A / gcd( A , C );
cnt++;
}
执行完代码后,原式变成
我们继续设
,其中
,
,
,用BSGS算法搞定。
因为
,要求
,实际上存在
的情况,直接套用会漏掉一些情况,对此我们要先进行一次
的枚举(后面会讲到它的替代方法),从0到
枚举
,直接验证
。
最后再说一些特判,如果在消去公因子的时候,出现
的情况,就已得出了答案,返回当时的
的值(这个操作与那个
的枚举等价);如果
等于1,直接返回0,其余特判跟BSGS算法相同。
这里我就不用map了,直接上邻接表。
板子http://poj.org/problem?id=3243
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define ll long long
#define mod 100007
using namespace std;
struct hashtable{
int bcnt;
int head[mod];
struct line{
int next;
int to;
int val;
}str[10 * mod];
inline void insert( int from , int to , int val ) {
str[++bcnt].next = head[from];
head[from] = bcnt;
str[bcnt].to = to;
str[bcnt].val = val;
return;
}
inline void clear() {
bcnt = 0;
memset( head , 0 , sizeof(head) );
}
inline void doit( int x , int y ) {
int te = x % mod;
insert( te , y , x );
}
inline int query( int x ) {
int te = x % mod;
for ( int i = head[te] ; i ; i = str[i].next ) {
int sn = str[i].to;
int va = str[i].val;
if ( va == x ) {
return sn;
}
}
return -1;
}
}hash;
inline int gcd( int a , int b ) {
return b == 0 ? a : gcd( b , a % b );
}
inline int power( int a , int b , int p ) {
int res = 1;
a %= p;
while (b) {
if ( b & 1 ) {
res = ( (ll)res * (ll)a ) % p;
}
a = ( (ll)a * (ll)a ) % p;
b >>= 1;
}
return res;
}
void c() {
printf("yes\n");
}
inline int exbsgs( int a , int b , int p ) {//若无解则返回-1
a %= p;
b %= p;
if ( b == 1 ) {
return 0;//特判
}
if ( !a ) {
return b == 0 ? 1 : -1;//特判
}
int g , cnt = 0;
ll d = 1;
while ( gcd( a , p ) > 1 ) {
g = gcd( a , p );
if ( b % g ) {
return -1;
}
b /= g;
p /= g;
++cnt;
d = d * ( a / g ) % p;
if ( b == d ) {
return cnt;
}
}
hash.clear();
int t = (int)(sqrt(p * 1.0)) + 1;
for ( int j = 0 ; j <= t - 1 ; ++j ) {
int val = ( (ll)b * (ll)power( a , j , p ) ) % p;
hash.doit( val , j );
}
a = power( a , t , p );
for ( int i = 0 ; i <= t ; ++i ) {
int val = power( a , i , p );
val = ((ll)val * (ll)d) % p;//不要落了d
int temp = hash.query(val);
if ( temp == -1 ) {
continue;
}
if ( i * t - temp + cnt >= 0 ) {
return i * t - temp + cnt;
}
}
return -1;
}
int main () {
int a , b , p;
while ( scanf("%d%d%d",&a,&p,&b) != EOF && a && b && p ) {
int tem = exbsgs( a , b , p );
if ( tem == -1 ) {
printf("No Solution\n");
} else {
printf("%d\n",tem);
}
}
return 0;
}
讲了这么多,我们终于得到了
的解法,那么第二个公式怎么办(⊙﹏⊙)?难道又要自闭吗 ?
我放在下一篇讲吧,这涉及到阶,原根,指标的知识。
本文参考资料:
《算法竞赛进阶指南》
《信息学竞赛之数学一本通》
百度百科