【bzoj2242】[SDOI2011]计算器 快速幂+exgcd+BSGS

Description

你被要求设计一个计算器完成以下三项任务:
1、给定y,z,p,计算Y^Z Mod P 的值;
2、给定y,z,p,计算满足xy≡ Z ( mod P )的最小非负整数;
3、给定y,z,p,计算满足Y^x ≡ Z ( mod P)的最小非负整数。

Input

输入包含多组数据。

第一行包含两个正整数T,K分别表示数据组数和询问类型(对于一个测试点内的所有数据,询问类型相同)。
以下行每行包含三个正整数y,z,p,描述一个询问。

Output

对于每个询问,输出一行答案。对于询问类型2和3,如果不存在满足条件的,则输出“Orz, I cannot find x!”,注意逗号与“I”之间有一个空格。

Sample Input

【样例输入1】

3 1

2 1 3

2 2 3

2 3 3

【样例输入2】

3 2

2 1 3

2 2 3

2 3 3

【数据规模和约定】

对于100%的数据,1<=y,z,p<=10^9,为质数,1<=T<=10。

Sample Output

【样例输出1】

2

1

2

【样例输出2】

2

1

0

HINT

Source

第一轮day1


第一个快速幂,没意思。
第二个exgcd,在这:【poj1061】青蛙的约会 exgcd解同余方程
第三个是刚学的BSGS…

BSGS,就是大步小步法,用来解这个方程:

AxB(modC)

x=im+ ,则上面的式子就变为:

Aj(Am)iB(modC)

于是可以枚举 Aj ,然后算出 Am 的逆元,然后枚举它的i次方再乘b,查表即可。

因为x最大不会超过C,所以 m=ceil(sqrt(C)) 最优,可以结合分块思想去想。

其实也相当于中途相遇法。

我也不知道如何证明正确性……几个小时之前证了一下现在发现不大对……不过确实是A掉了…

关于 Am 的逆元,因为费马小定理 An11(modn) ,所以 AmAn1m1(modn) ,所以 Am 的逆元是 An1m

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
using namespace std;

typedef long long LL;

const int SZ = 1000010;
const LL MOD = 1000007;

LL ksm(LL a,LL b,LL mod)
{
    LL ans = 1;
    while(b)
    {
        if(b & 1) ans = ((ans % mod) * (a % mod)) % mod;
        a = ((a % mod) * (a % mod)) % mod;
        b >>= 1;
    }
    return ans;
}

LL exgcd(LL a,LL b,LL &x,LL &y)
{
    if(b == 0)
    {
        x = 1; y = 0;
        return a;
    }
    LL d = exgcd(b,a % b,x,y);
    LL t = x; x = y; y = t - a / b * y;
    return d;
}

LL get_exgcd(LL a,LL b,LL c) //ax+by=c
{
    LL x,y;
    LL d = exgcd(a,b,x,y);
    if(c % d) return -1;
    return ((x * (c / d)) % (b / d) + (b / d)) % (b / d);
}

map<LL,LL> hash;

LL BSGS(LL a,LL b,LL mod) //a^x = b (% mod)
{
    a %= mod; b %= mod;
    if(a == 0 && b == 0) return 1;
    else if(a == 0) return -1;
    hash.clear();
    LL m = ceil(sqrt(mod));
    LL amni = ksm(a,mod - m - 1,mod);
    LL t = 1;
    hash[t] = 0;
    for(int i = 1;i < m;i ++)
    {
        t = (t * a) % mod;
        if(t != 1 && !hash[t]) 
            hash[t] = i;
    }
    for(int i = 0;i <= m - 1;i ++)
    {
        LL ans = hash[b];
        if(b == 1 || ans)
        {
            return i * m + ans;
        }
        b = (b * amni) % mod;
    }   
    return -1;
}

int main()
{
    int T,k;
    scanf("%d%d",&T,&k);
    while(T --)
    {
        LL a,b,c;
        scanf("%lld%lld%lld",&a,&b,&c);
        if(k == 1)
        {
            printf("%lld\n",ksm(a,b,c));
        }
        else if(k == 2)
        {
            LL ans = get_exgcd(a,c,b);
            if(ans == -1) puts("Orz, I cannot find x!");
            else printf("%lld\n",ans);
        }
        else
        {
            LL ans = BSGS(a,b,c);
            if(ans == -1) puts("Orz, I cannot find x!");
            else printf("%lld\n",ans);          
        }
    }
    return 0;
}

/*
10 3
1000052719 255392610 79791109
146324954 14948940 73162477
703874049 508100543 1037977027
260580983 889231132 496040033
691085033 402703206 539487719
752770845 539141408 817774367
974839853 763383053 22685989
855218194 362010502 198721379
900451466 1064373182 697990169
13892212 176820695 856980737



*/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值