[省选前题目整理][SGU 261]Discrete Roots(扩展欧几里得+中国剩余定理+原根+大步小步算法)

8 篇文章 0 订阅
1 篇文章 0 订阅

题目链接

http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=22207

题目大意

已知 kyp ,求 xky(modp) 的所有可行解

思路

首先,我们求出 modp 的原根 g
不妨设gqy(modp) q 可以通过大步小步算法求得。
由于在1x<p时, modp 意义下所有的数字均可以用 g 的幂次表示。因此一定存在等式gik(modp1)。这个可以通过中国剩余定理找出所有的可行解。

—————–以下来闲扯一下大步小步算法——————-
大步小步算法看上去非常的暴力,个人认为其思路就是基于分块思想。假设已知 xyp ,求 xky(modp)k ,那么首先我们要对于所有的 ip ,在map中记录下 xi 对应于 i 的映射。然后维护一个值xt,初始时 t=0 ,每次 t+=p ,若在map中能够查找到 yxt(m2) (这个操作复杂度只是对数),那么就找到了答案, k=t+mp[yxt(m2)] mp[yxt(m2)]=yxt(m2) map中映射的幂

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <vector>
#include <map>
#include <cmath>

using namespace std;

typedef long long int LL;

vector<LL>divisor,ans;

LL extGCD(LL a,LL b,LL &x,LL &y)
{
    if(!b)
    {
        x=1;
        y=0;
        return a;
    }
    LL gcd=extGCD(b,a%b,x,y);
    LL tmp=x;
    x=y;
    y=tmp-a/b*y;
    return gcd;
}

LL fastPow(LL base,LL pow,LL mod)
{
    LL ans=1;
    while(pow)
    {
        if(pow&1) ans=(ans*base)%mod;
        base=(base*base)%mod;
        pow>>=1;
    }
    return ans;
}

bool g_test(LL g,LL p) //检查g是否是mod p的原根
{
    for(int i=0;i<(int)divisor.size();i++)
        if(fastPow(g,(p-1)/divisor[i],p)==1)
            return false;
    return true;
}

LL PrimitiveRoot(LL p) //求mod p的原根
{
    divisor.clear();
    LL tmp=p-1;
    for(LL i=2;i*i<=tmp;i++)
    {
        if(tmp%i==0)
        {
            divisor.push_back(i);
            while(tmp%i==0) tmp/=i;
        }
    }
    if(tmp!=1) divisor.push_back(tmp);
    LL g=0; //爆枚原根g
    while(++g)
        if(g_test(g,p))
            return g;
    return -1;
}

LL DiscreteLog(LL x,LL n,LL m) //求离散对数,x^y=n(mod m),求y
{
    map<LL,LL>table; //一个有序表
    LL sqrtm=(LL)(sqrt(m)+0.5);
    LL tmp=1; //x^i=tmp
    for(int i=0;i<sqrtm;i++)
    {
        table[tmp]=i;
        tmp=tmp*x%m;
    }
    LL step=tmp; //t=x^sqrt(m)
    tmp=1;
    for(int i=0;i<sqrtm;i++)
    {
        LL d=n*fastPow(tmp,m-2,m)%m;
        if(table.count(d))
            return i*sqrtm+table[d];
        tmp=tmp*step%m;
    }
    return -1;
}

void CRT(LL a,LL b,LL n) //ax=b(mod n)
{
    LL x,y,gcd;
    gcd=extGCD(a,n,x,y);
    if(b%gcd==0) //有解
    {
        x=(x%n+n)%n;
        ans.push_back(x*(b/gcd)%(n/gcd));
        for(LL i=1;i<gcd;i++)
            ans.push_back((ans[0]+i*n/gcd)%n);
    }
}

int main()
{
    LL a,p,g,q,k;
    while(scanf("%lld%lld%lld",&p,&k,&a)!=EOF) //x^k=a(mod p)
    {
        if(!a) //a=0要特判,x只能为0
        {
            printf("1\n0\n");
            continue;
        }
        g=PrimitiveRoot(p); //g=mod p的原根
        q=DiscreteLog(g,a,p); //g^q=a(mod p)
        CRT(k,q,p-1); //问题变为kx=q(mod p-1)的所有正整数解
        for(int i=0;i<(int)ans.size();i++)
            ans[i]=fastPow(g,ans[i],p); //ans[i]=g^x mod p
        sort(ans.begin(),ans.end());
        printf("%d\n",(int)ans.size());
        for(int i=0;i<(int)ans.size();i++) printf("%lld\n",ans[i]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值