http://www.cnblogs.com/w007878/p/3621653.html?utm_source=tuicool
建议先阅读上面的博客,再来看我的解题。具体解题在分割线下面
原题地址:http://acm.sgu.ru/problem.php?contest=0&problem=261
题目大意:给出质数 p 、 k 和一个自然数 a ,求关于 x 的同余方程 xk≡a(modp) 在区间 [0,p−1] 内所有解
数据范围: 2≤p≤109,2≤k≤100000
这种同余方程叫做“k次剩余”,这题也是一个模板题,但它涉及了比较多的数论算法,我会一一在这里尽可能讲清楚,这些概念和算法包括:
原根、缩系、离散对数、乘法逆元、线性同余方程、快速幂算法、扩展欧几里德算法、Baby Step - Gaint Step算法
虽然我希望近可能地系统化讲解,但是以下基本概念不会做任何讲解,不懂的同学可以查到大量的相关资料,它们包括:
素数、带余除法、模运算、幂运算、欧拉函数 φ(n) 、渐进复杂度分析
首先我们通过引入一些概念对所求进行一些变形,最后再讲述求解所需的算法,前面的理论定义部分可能较为枯燥:
阶、原根、缩系与离散对数:
定义 1(阶):若 gcd(a,m)=1 ,满足 ar≡1(modm) 的最小正整数 r 称为 a 模 m 的阶
性质 1(阶的判定定理): gcd(a,m)=1 时, a 模 m 的阶是 r 等价于以下两点:
1: ar≡1(modm)
2:对于 r 的每一个质因子 p ,均有 arp≢1(modm)
证明:第一条是阶的定义,第二条应用反证法,若存在质因子 p 使得其不成立,则与 r 的最小性矛盾
定义 2(原根):若整数 g 模 m 的阶为 φ(m) ,则称 g 为 m 的原根
性质 2(原根的存在情况):正整数 m 存在原根,当且仅当 m=2,4,pa,2pa ,其中 p 是奇素数且 a≥1
由性质2我们可以得知所有的质数都有原根。
性质 3(原根的分布):一个数可能对应多个原根,而且最小的原根一般都很小(基本没例外)
定义 3(缩系):若模 m 有原根 g ,则 {g0,g1,…,gφ(m)−1} 称为模 m 的缩系。显然对于每个与 m 互质的正整数 a ,在 m 的缩系中仅存在唯一的一个整数 k ,使得 gr≡a(modm)
定义 4(离散对数):我们将形如 ak≡n(modm) (m是质数)的式子中的 k 称为 n 以 a 为底在模 m 下的离散对数,特别地,在 a 为 m 的原根 g 时,我们称 k 为 n 模 m 的离散对数,由于 k 的值与原根 g 有关,我们将它记作 indgn
由于我们讨论的模是质数,所以我们可以将不超过 m 的运算全部放到它的缩系里面来做了。而且不超过 m 的整数 n 模 m 的离散对数正巧与 m 的缩系中 n 对应的值相等。
性质 4(离散对数运算性质):
类似于对数的运算性质,离散对数的性质大致有以下几条:
1:若 gcd(a,m)=gcd(b,m) , a≡b(modm) 等价于 indga=indgb
2: indgab=indga+indgb(modφ(m))
3: indgan=nindga(modφ(m))
以上涉及的内容全在整数范围内
好了,进行了那么多定义和基础知识的灌输,题目涉及的理论基础也差不多说完了,让我们来回归题目本身吧:
xk≡a(modp) 等价于 kindgx≡indga(modφ(p)) ,其中 g 是模 p 的原根(这个过程强烈建议读者手推)。这样,我们直接求解这个线性同余方程就好了,问题被分解为了求原根、离散对数以及求解线性同余方程。
*********************************************************************************************************************************分割线,现在开始解题:
*********************************************************************************************************************************
首先需要一些基础的知识点:快速幂,欧几里得,扩展欧几里得
LL pow_mod(LL a,LL i,LL n)//快速幂 求 a^i(mod n) 的值
{
if(i==0) return 1%n;
LL temp=pow_mod(a,i>>1,n);
temp=temp*temp%n;
if(i&1) temp=temp*a%n;
return temp;
}
LL gcd(LL a,LL b){return b==0?a:gcd(b,a%b);}//欧几里得
LL extend_gcd(LL a,LL b,LL &x,LL &y)//扩展欧几里得
{
if(b==0){x=1;y=0;return a;}
else {
int r=extend_gcd(b,a%b,y,x);
y-=x*(a/b);
return r;
}
}
第一步求原根的模板:
vector<LL>a;
bool g_test(LL g,LL p)
{
for(LL i=0;i<a.size();i++)
if(pow_mod(g,(p-1)/a[i],p)==1) return 0;
return 1;
}
LL primitive_root(LL p)//求原根
{
a.clear();
LL tmp = p-1;
for(LL i=2;i<=tmp/i;i++)
if(tmp%i==0){
a.push_back(i);
while(tmp%i==0)
tmp/=i;
}
if(tmp!=1) a.push_back(tmp);
LL g=1;
while(true){
if(g_test(g,p)) return g;
++g;
}
}
第二步转化之后需要求两个值,一是log(g,a) {即求离散对数,用大步小步算法 或 叫Giant-Step Baby-Step },二是phi(p)
map<LL,LL>rec;
LL discrete_log(LL x,LL n,LL m)//求离散对数 x^y=n(mod m)
{
rec.clear();
LL s=(LL)(sqrt((double)m ) );
for(;s*s<=m;)s++;
LL cur=1;
for(int i=0;i<s;++i){
rec[cur]=i;
cur=cur*x%m;
}
LL mul=cur;
cur=1;
for(int i=0;i<s;++i){
LL more = n*pow_mod(cur,m-2,m)%m;
if(rec.count(more)){
return i*s+rec[more];
}
cur=cur*mul%m;
}
return -1;
}
LL euler_phi(LL n)//求n的欧拉函数值
{
LL m=(LL)sqrt(n+0.5);
LL ans=n;
for(LL i=2;i<=m;i++)if(n%i==0){
ans=ans/i*(i-1);
while(n%i==0)n/=i;
}
if(n>1) ans=ans/n*(n-1);
return ans;
}
第三步要知道这就是一个单变元模线性方程了(属于 线性同余方程,但这个只有一个未知数,且这个未知数有多种取值)
第四步,求解单变元模线性方程
vector<LL>ans;
void line_mod_equation(LL a,LL b,LL n)//单变元模线性方程 a*x=b(mod n),求x的解集
{
LL x,y;
LL d=extend_gcd(a,n,x,y);
ans.clear();
if(b%d==0){
x=(x%n+n)%n;
ans.push_back(x*(b/d)%(n/d));
for(LL i=1;i<d;++i)
ans.push_back((ans[0]+i*n/d)%n);
}
}
第五步。因为ans解集存的是log(g,x)。所以用快速幂转换成原来的x
总代码:
#include<vector>
#include<cmath>
#include<algorithm>
#include<string>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
typedef long long LL;
using namespace std;
LL pow_mod(LL a,LL i,LL n)//快速幂 a^i(mod n)
{
if(i==0) return 1%n;
LL temp=pow_mod(a,i>>1,n);
temp=temp*temp%n;
if(i&1) temp=temp*a%n;
return temp;
}
//求原根
vector<LL>a;
bool g_test(LL g,LL p)
{
for(LL i=0;i<a.size();i++)
if(pow_mod(g,(p-1)/a[i],p)==1) return 0;
return 1;
}
LL primitive_root(LL p)//求原根
{
a.clear();
LL tmp = p-1;
for(LL i=2;i<=tmp/i;i++)
if(tmp%i==0){
a.push_back(i);
while(tmp%i==0)
tmp/=i;
}
if(tmp!=1) a.push_back(tmp);
LL g=1;
while(true){
if(g_test(g,p)) return g;
++g;
}
}
LL euler_phi(LL n)//求n的欧拉函数值
{
LL m=(LL)sqrt(n+0.5);
LL ans=n;
for(LL i=2;i<=m;i++)if(n%i==0){
ans=ans/i*(i-1);
while(n%i==0)n/=i;
}
if(n>1) ans=ans/n*(n-1);
return ans;
}
//大步小步算法 或 叫Giant-Step Baby-Step
map<LL,LL>rec;
LL discrete_log(LL x,LL n,LL m)//求离散对数 x^y=n(mod m)
{
rec.clear();
LL s=(LL)(sqrt((double)m ) );
for(;s*s<=m;)s++;
LL cur=1;
for(int i=0;i<s;++i){
rec[cur]=i;
cur=cur*x%m;
}
LL mul=cur;
cur=1;
for(int i=0;i<s;++i){
LL more = n*pow_mod(cur,m-2,m)%m;
if(rec.count(more)){
return i*s+rec[more];
}
cur=cur*mul%m;
}
return -1;
}
LL gcd(LL a,LL b){return b==0?a:gcd(b,a%b);}
LL extend_gcd(LL a,LL b,LL &x,LL &y)//扩展欧几里得
{
if(b==0){x=1;y=0;return a;}
else {
int r=extend_gcd(b,a%b,y,x);
y-=x*(a/b);
return r;
}
}
vector<LL>ans;
void line_mod_equation(LL a,LL b,LL n)//单变元模线性方程 a*x=b(mod n),求x的解集
{
LL x,y;
LL d=extend_gcd(a,n,x,y);
ans.clear();
if(b%d==0){
x=(x%n+n)%n;
ans.push_back(x*(b/d)%(n/d));
for(LL i=1;i<d;++i)
ans.push_back((ans[0]+i*n/d)%n);
}
}
vector<LL>ans2;
int main()
{
//freopen("f:\\1.txt","r",stdin);
LL p,k,a;
cin>>p>>k>>a;
if(a>=p) puts("0");
else if(a==0){
printf("1\n0\n");
}else{
LL g=primitive_root(p);//求出原根g
LL phi_p=euler_phi(p);//求欧拉函数值,质数就是p-1;
LL ga=discrete_log(g,a,p);//log(g,a)
line_mod_equation(k,ga,phi_p);//解方程: k*x=ga(mod phi_p)
ans2.clear();
for(int i=0;i<ans.size();i++){
LL ans_x=pow_mod(g,ans[i],p);//log(g,x) 还原成x
ans2.push_back(ans_x);
}
sort(ans2.begin(),ans2.end());//排序
int sz=unique(ans2.begin(),ans2.end())-ans2.begin();//去重
printf("%d\n",sz);
for(int i=0;i<sz-1;i++)
cout<<ans2[i]<<" "; //printf("%d ",ans2[i]);
if(sz>0) cout<<ans2[sz-1];
printf("\n");
}
return 0;
}
交题可以去vjudge:
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=22207