hdu 5377 Root 原根+离散对数+扩展欧几里得

题意:给定一个数字sum,有 m 个询问:(xi, yi),求最小的非负整数 ki 满足 xi^ki =yi (mod p)。

            其中 p 是 sum 的质因子。

            1<=sum<=10^8,1<=m<=10^5,0<=xi, yi<=10^9

分析:

直接用离散对数求解K的复杂度为O( m log m ),m为根号n。对于该题每次询问都要单独计算,复杂度太高。

需要用到原根,离散对数,扩展欧几里得计算。

整体思路:

对sum分解质因数, 对于x^k mod p = y ,

先求出p的原根d,对同余式两边取对数得到  k*logd(x) mod p-1 = logd(y)

再使用扩展欧几里德求出上式中的k

复杂度优化!!:

令:A=logd(x)  B=logd(y)

对同余式取对数时:即 求d^A mod p = x      d^B mod p = y

baby step中要对d^m打表,由于对于同一质因子p,d是相同的。所以可以提前初始化表。

那么总复杂度变为O(m+p/m*q),p为质数大小,m为表长,q为查询次数

其中查询次数 q 为10^5,所以表要足够大,以使p/m*q这一项较小。


#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#include <vector>
#include <map>
#include <tr1/unordered_map>
#define clr(x, y) memset(x, y, sizeof x)
using namespace std;
typedef long long LL;
const double eps=1e-8;
const int maxn=100100;
const int mod=1e6+10;
LL prime[maxn];
LL X[maxn],Y[maxn];
LL A[maxn],B[maxn];
LL ans[maxn];
LL M;

//质因数分解
LL get_prime(LL n)
{
    LL cnt=0;
    for(LL i=2;i*i<=n;i++)
    {
        if(n%i==0)
        {
            prime[cnt++]=i;
            while(n%i==0)
                n/=i;
        }
    }
    if(n!=1)
        prime[cnt++]=n;
    return cnt;
}

//快速幂
LL pow_mod(LL a,LL p,LL n)
{
    LL ans=1;
    while(p)
    {
        if(p&1)
            ans=(ans*a)%n;
        p>>=1;
        a=(a*a)%n;
    }
    return ans;
}

//求原根
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)
{
    LL tmp=p-1;
    a.clear();
    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++;
    }
}

//扩展欧几里得
void gcd(LL a,LL b,LL &d,LL &x,LL &y)
{
    if(!b) {  d=a;  x=1;  y=0;}
    else
    {
        gcd(b,a%b,d,y,x);
        y-=x*(a/b);
    }
}

//求逆元
LL inv(LL a,LL n)
{
    LL d,x,y;
    gcd(a,n,d,x,y);
    return d==1 ? (x+n)%n : -1;
}

//手动哈希
struct HashTable
{
    int top,head[mod];
    struct Node
    {
        int x,y,next;
    }node[mod];
    void init()
    {
        top=0;
        memset(head,0,sizeof(head));
    }
    void insert(LL x,LL y)
    {
        node[top].x=x;
        node[top].y=y;
        node[top].next=head[x%mod];
        head[x%mod]=top++;
    }
    LL query(LL x)
    {
        for(int tx=head[x%mod];tx;tx=node[tx].next)
        {
            if(node[tx].x==x)
                return node[tx].y;
        }
        return -1;
    }
}hs;

//求出所有查询中X,Y关于a(mod n)的离散对数
void log_mod(LL a,LL n)
{
    LL m,v,e=1;
    m=mod;
    v=inv(pow_mod(a,m,n),n);
    //打表 a^0 ~ a^m-1, m为表长
    hs.init();
    for(int i=0;i<m;i++)
    {
        hs.insert(e,i);
        e=e*a%n;
    }
    //求出所有查询中X,Y关于a的离散对数
    for(int j=0;j<M;j++)
    {
        //必须要取模后再计算
        LL b=X[j]%n;
        for(int i=0;i<m;i++)
        {
            LL s=hs.query(b);
            if(s!=-1)
            {
                A[j]=i*m+s;
                break;
            }
            b=b*v%n;
        }
        b=Y[j]%n;
        for(int i=0;i<=n/m;i++)
        {
            LL s=hs.query(b);
            if(s!=-1)
            {
                B[j]=i*m+s;
                break;
            }
            b=b*v%n;
        }
    }
}

//用扩展欧几里得求a*x+b*y=c中x的最小非负整数解
LL get_K(LL a,LL b,LL c)
{
    LL d,x,y;
    gcd(a,b,d,x,y);
    if(c%d!=0)
        return -1;
    x=x*c/d;
    b=b/d;
    x=x-(x/b*b);
    if(x<0)
        x+=b;
    return x;
}

int main()
{
    int T;
    LL sum;
    scanf("%d",&T);
    for(int t=1;t<=T;t++)
    {
        memset(ans,-1,sizeof(ans));
        scanf("%I64d %I64d",&sum,&M);
        printf("Case #%d:\n",t);
        for(int i=0;i<M;i++)
        {
            scanf("%I64d %I64d",&X[i],&Y[i]);
        }
        //质因数分解
        LL cnt=get_prime(sum);
        LL temp;
        //枚举所有质因子
        for(int j=0;j<cnt;j++)
        {
            //求该质因子的原根d
            LL d=primitive_root(prime[j]);
            //求所有的查询中X,Y关于d(mod prime[j])的离散对数
            log_mod(d,prime[j]);
            for(int i=0;i<M;i++)
            {
                //求解最小的K, K*A[i]=B[i](mod (prime[j]-1))
                temp=get_K(A[i],prime[j]-1,B[i]);
                if(temp!=-1)
                {
                    if(ans[i]==-1)
                        ans[i]=temp;
                    else
                        ans[i]=min(temp,ans[i]);
                }
            }
        }
        for(int i=0;i<M;i++)
            printf("%I64d\n",ans[i]);
    }
    return 0;
}


你可能感兴趣的:(扩展欧几里得,原根,离散对数)