例题 :POJ - 2417
求 a^x = b (mod n) 的最小非负解x 。 ( gcd(a,n)==1 )
分析: 本次分析 只针对 gcd(a,n)==1的情况 。
首先 我们再把问题转化一下 我们将n设置为素数,n不是素数一会讨论。
现在我们已知 gcd(a,n)==1, p为素数.
根据以上条件,我们可以利用费马小定理 a^(p-1) =1(mod p) 知道 这个式子的周期为p-1,(注意这里p-1只是其一个周期,不一定为最小正周期),知道其一个周期了,那么我们遍历[0,p) 第一个成立不就是最小的解? 理论可行,但是时间复杂度还是远远不行。 接下来我们来优化这个思路(其实还是暴力,不过是更加的优雅 QAQ ) 。
这里设 x= i*m-j ,其中m=ceil(sqrt(p)) . 0< i <=m . 0 <= j < m .
你肯定会问 为什么这么设?麻烦你现在利用上述条件将x的取值范围确定出来, 发现没有?x的范围 为[0,m*m] ,然后其覆盖了式子的那个周期p-1.
然后我们将x带入原式子得 a^(i*m) = b * a^j (mod p ) , 然后我们从小到大遍历j,将 b * a^j , j 插入hash中,为什么要从小到大遍历呢? 因为有的时候不同的j会产生相同的值,然后因为我们是从小到大遍历的,覆盖的时候最后的 j 一定是大的,这样的话,对于相同的i,j是更大的最后的 值 i * m - j一定是最小的 , 同样的我们再从小到大遍历i 就行了,第一个能够在hash中找到的一定是最小的值 x = i * m - j 。
现在我们在讨论一下n不是素数的情况,其实也很简单 ,只要gcd(a,n)==1条件成立 ,则一定有欧拉定理 a^phi(n)=1(mod p)成立,其最小解一定在phi(n)内,又因为 phi( n ) < n 所以我们只要遍历到n足够了,所以n是不是素数都足够解了 。
kuangbin的板子
模板条件 : gcd(a,n)==1 .
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
using namespace std;
#define state intconst int N = 100000+3;
const int MAXM = 1e6;
const int mod = 7653;
const int inf = 0x3f3f3f3f;struct HASH{state hs;int id,next;
}MAP[N+3];
int head[mod],top;
void init(){memset(head,-1,sizeof(head));top=1;
}
void Insert(state a,int b){int c=a%mod;MAP[top].hs=a; MAP[top].id=b; MAP[top].next=head[c];head[c]=top++;
}
int Find(state x){int c=x%mod;for(int i=head[c];i!=-1;i=MAP[i].next){HASH h=MAP[i];if(h.hs==x) return h.id;}return -1;
}int BSGS(int a,int b,int n){ // n是不是素数都可以init();if(b==1) return 0;int m=sqrt(n*1.0);int j;long long x=1,p=1;for(int i=0;i<m;++i,p=p*a%n) Insert(p*b%n,i);for(long long i=m;;i+=m){if((j=Find(x=x*p%n))!=-1) return i-j;if(i>n) break;}return -1;
}int main(){int a,b,p;while(cin>>p>>a>>b){int ans=BSGS(a,b,p);if(ans==-1) puts("no solution");else cout<<ans<<endl;}
return 0;
}