SGU261 一些数学知识入门与应用结合 原根 快速幂 离散对数 扩展欧几里得 单边元模线性方程

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 的同余方程 xka(modp) 在区间 [0,p1] 内所有解

数据范围: 2p109,2k100000

这种同余方程叫做“k次剩余”,这题也是一个模板题,但它涉及了比较多的数论算法,我会一一在这里尽可能讲清楚,这些概念和算法包括:

原根、缩系、离散对数、乘法逆元、线性同余方程、快速幂算法、扩展欧几里德算法、Baby Step - Gaint Step算法

虽然我希望近可能地系统化讲解,但是以下基本概念不会做任何讲解,不懂的同学可以查到大量的相关资料,它们包括:

素数、带余除法、模运算、幂运算、欧拉函数 φ(n) 、渐进复杂度分析

 

 首先我们通过引入一些概念对所求进行一些变形,最后再讲述求解所需的算法,前面的理论定义部分可能较为枯燥:

阶、原根、缩系与离散对数:

定义 1(阶): gcd(a,m)=1 ,满足 ar1(modm) 的最小正整数 r 称为 a m

性质 1(阶的判定定理): gcd(a,m)=1 时, a m 的阶是 r 等价于以下两点:

  1: ar1(modm)

  2:对于 r 的每一个质因子 p ,均有 arp≢1(modm)

  证明:第一条是阶的定义,第二条应用反证法,若存在质因子 p 使得其不成立,则与 r 的最小性矛盾

定义 2(原根):若整数 g m 的阶为 φ(m) ,则称 g m 原根

性质 2(原根的存在情况):正整数 m 存在原根,当且仅当 m=2,4,pa,2pa ,其中 p 是奇素数且 a1

由性质2我们可以得知所有的质数都有原根。

性质 3(原根的分布):一个数可能对应多个原根,而且最小的原根一般都很小(基本没例外)

定义 3(缩系):若模 m 有原根 g ,则 {g0,g1,,gφ(m)1} 称为模 m 缩系。显然对于每个与 m 互质的正整数 a ,在 m 的缩系中仅存在唯一的一个整数 k ,使得 gra(modm)

定义 4(离散对数):我们将形如 akn(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) ab(modm) 等价于 indga=indgb

  2: indgab=indga+indgb(modφ(m))

  3: indgan=nindga(modφ(m))

以上涉及的内容全在整数范围内

 

好了,进行了那么多定义和基础知识的灌输,题目涉及的理论基础也差不多说完了,让我们来回归题目本身吧:

xka(modp) 等价于 kindgxindga(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


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值