【哈希表除留余数法+BSGS算法求离散对数】POJ Discrete Logging 2417

Discrete Logging

Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 4443 Accepted: 2028

Description

Given a prime P, 2 <= P < 2 31, an integer B, 2 <= B < P, and an integer N, 1 <= N < P, compute the discrete logarithm of N, base B, modulo P. That is, find an integer L such that
    BL == N (mod P)

Input

Read several lines of input, each containing P,B,N separated by a space.

Output

For each line print the logarithm on a separate line. If there are several, print the smallest; if there is none, print “no solution”.

Sample Input

5 2 1
5 2 2
5 2 3
5 2 4
5 3 1
5 3 2
5 3 3
5 3 4
5 4 1
5 4 2
5 4 3
5 4 4
12345701 2 1111111
1111111121 65537 1111111111

Sample Output

0
1
3
2
0
3
1
2
0
no solution
no solution
1
9584351
462803587

Hint

The solution to this problem requires a well known result in number theory that is probably expected of you for Putnam but not ACM competitions. It is Fermat’s theorem that states
   B(P-1) == 1 (mod P)
for any prime P and some other (fairly rare) numbers known as base-B pseudoprimes. A rarer subset of the base-B pseudoprimes, known as Carmichael numbers, are pseudoprimes for every base between 2 and P-1. A corollary to Fermat’s theorem is that for any m
   B(-m) == B(P-1-m) (mod P) .

题意:

求解离散对数,B^L≡N(mod P). L的最小值。

解题思路:

本人博客

这应该是最基础的求解离散对数。利用求解离散对数的经典算法:BSGS算法(Baby-Step Giant-Step 算法)此题给的P是素数,所以不用扩展BSGS算法,使用原始算法就可以了。最好有一定的预备知识:扩展欧几里得、求逆元、欧拉函数、费马小定理,有一定的知识可以更快的掌握这种算法。

这里就谈谈我对求解BSGS算法的理解,欢迎指出不足!学习的时候在网上找了很久都没有详细的讲解,所以看了很长时间才看懂这个算法。PS:主要还是本人比较弱。。。

  1. 由费马小定理或者欧拉定理我们可以得出(题目Hint也有给):
    B(P-1) ≡ 1 (mod P)
    这里因为P比较大,并且我们需要求的L可能也比较大,普通暴力算法肯定不行。我们可以考虑优化一下。利用欧拉定理我们知道:B^L mod P = B^(L% φ(P)) mod P. 所以,我们可以考虑不用枚举所有的值,L的值最多是在φ(P)之内,不过,因为本题给的数P是素数,所以φ(P)=P-1,如果只有这个优化肯定还是不可以。所以我们得想到继续优化。我们可以考虑把,L这个值换算一下:L=i*M+r. M=ceil(√P) .  
  2. *为什么M的值为此值?如果值是这个值的话枚举复杂度较低。因为任何一个数都可以表示b=k*a+r的形式,所以我们这里取ceil(√P)值为M值在这个算法里可以有效降低复杂度,使其为log(P)。可以参照AC代码:我们需要遍历所有的r值,和i值,这样可以保证出现所有的可能值,又能保证复杂度最低。如果M向r,i的任何一个值偏斜,即r或i任何一个值变大,都会导致复杂度变大。然后我们就可以把等式写成B^(i*M)*B^j≡N( mod P) ⇒(B^M)^i*B^j≡N ( mod P) 的形式。
  3.  
  4. *我们怎么查找到最小的L值?就是我们可以枚举i的值,计算得到B^M^i的值,我们用A暂且代表这个值,x代表B^j。然后我们得到A*x≡N( mod P) ,此时我们就是计算x值,这一步就是求逆元就可以了。因为B^j最大就是B^M,所以计算出x之后如果我们得到的这个数在[0-B^M],范围出现过,那么就说明这个解可行,因为我们是从小到大遍历i值,所以我们是从小到大得到的可行L的解,只要第一次找到可行解,那么这个可行解一定是最小的。在这时候跳出循环返回值即可。
  5.  
  6. 取余运算的时候注意取余的值。需要掌握欧拉定理。
  7.  
  8. 为了更快的查找到值,我们采用hash表的结构,使用:hash算法之除留余数法 记录[0-B^M]区间每个出现的值。

AC代码:

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>

using namespace std;

const int MAXN = 1000000;
typedef long long LL;

bool hash[MAXN];
LL p[MAXN];
LL var[MAXN];

//hash算法-除留余数法
void insertx(LL n,LL v)
{
    int x=v%MAXN;
    while(hash[x]&&var[x]!=v){//这一步是筛重
        x++;
        if(x==MAXN)
            x=0;
    }
    if(!hash[x]){
        var[x]=v;//记录v,即B^j的值
        hash[x]=true;
        p[x]=n; //记录值B^j中j的值
    }
}

LL find_x(LL v)
{
    int x=v%MAXN;
    while(hash[x]&&var[x]!=v){ //确保出来的值是B^j对照的j值
        x++;
        if(x==MAXN)
            x=0;
    }
    if(hash[x]==false)
        return -1;
    return p[x];
}

LL exgcd(LL a,LL b,LL &x,LL &y)
{
    if(b==0){
        x=1;
        y=0;
        return a; //返回公约数
    }
    LL r=exgcd(b,a%b,x,y);
    LL t=x;
    x=y;
    y=t-a/b*y;
    return r;
}

//离散对数:A^x≡B(mod P) 求x。
LL BSGS(LL A,LL B,LL P)
{
    LL num=1;
    int M=ceil(sqrt(P*1.0));
    for(int i=0;i<M;i++){
        insertx(i,num);
        num=num*A%P;
    }
    LL res=1;
    for(int i=0;i<M;i++){
        LL x,y;
        LL r=exgcd(res,P,x,y);//求逆元
        x=x*B;
        x=(x%(P/r)+(P/r))%(P/r);//取x的最小正值。r可省略,因为r=1.
        LL ans=find_x(x);
        if(ans!=-1){
            return (ans+i*M);
        }
        res=res*num%P;
    }
    return -1;
}

int main()
{
    LL N,B,P;
    while(scanf("%lld%lld%lld",&P,&B,&N)!=EOF){
        memset(hash,0,sizeof(hash));
        memset(var,-1,sizeof(var));
        memset(p,-1,sizeof(p));
        LL ans=BSGS(B,N,P);
        if(ans==-1) printf("no solution\n");
        else printf("%lld\n",ans);
    }
    return 0;
}

你可能感兴趣的:(C语言,poj)