BZOJ 2242 SDOI2011计算器

Problem

BZOJ传送门
洛谷传送门
Codevs传送门

Solution

对于第一种操作,快速幂即可。
对于第二种操作,等式两边同乘在模p意义下的y的逆元inv(y),则等式变为x≡(z×inv(y))%p (mod p)。
对于第三种操作,用BSGS。

BSGS利用了分块的思想,首先我们由费马小定理知道 yp11(modp) y p − 1 ≡ 1 ( m o d p ) ,也就是说只需要枚举到p-1即可,但我们希望优化。那么设m=ceil(sqrt(p)),则x可表示为km+i。也就是说 akm×aib(modp) a k m × a i ≡ b ( m o d p ) 。为了方便查表,我们用hash将a^i存起来。
法一:扩展欧几里得
不妨设 D=akm D = a k m ,则 D×aib(modp) D × a i ≡ b ( m o d p ) 。扩欧求解A^i,然后查表看存不存在。
法二:逆元
就相当于 aib×inv(akm)(modp) a i ≡ b × i n v ( a k m ) ( m o d p ) 。每次k自增时,等式两边同乘inv(a^m),然后查表。较法一快,逆元用费马小定理求即可。下方代码中用的是法二。

Code

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long ll;
const ll MAXX=((1<<30)-1)<<1|1;
struct hashtable{
    static const ll ha=999917,maxe=46340;
    ll E,lnk[ha],son[maxe+5],nxt[maxe+5],w[maxe+5];
    ll top,stk[maxe+5];
    void clear(){E=0;while(top) lnk[stk[top--]]=0;}
    void add(ll x,ll y){son[++E]=y;nxt[E]=lnk[x];w[E]=MAXX;lnk[x]=E;}
    bool count(ll y)
    {
        ll x=y%ha;
        for(int j=lnk[x];j;j=nxt[j]) if(y==son[j]) return true;
        return false;
    }
    ll& operator [] (ll y)
    {
        ll x=y%ha;
        for(int j=lnk[x];j;j=nxt[j]) if(y==son[j]) return w[j];
        add(x,y);stk[++top]=x;return w[E];
    }
}f;
ll n,k,ans,y,z,p;
inline ll min(int x,int y){return x<y?x:y;}
ll power(ll x,ll y,ll p)
{
    ll res=1;
    while(y)
    {
        if(y&1) res=res*x%p;
        x=x*x%p;
        y>>=1;
    }
    return res;
}
ll bsgs(ll a,ll b,ll c)
{
    if(c==1) return b?-1:(a!=1);
    if(b==1) return a?0:-1;
    if(a%c==0) return b?-1:1;
    ll m=ceil(sqrt(c+0.5)),base=1;ll v=power(a,c-m-1,c);
    f.clear();
    for(int i=0;i<=m-1;i++)
    {
        f[base]=min(f[base],i);
        base=(base*a)%c;
    }
    for(int i=0;i<=m-1;i++)
    {
        if(f.count(b)) return i*m+f[b];
        b=b*v%c;
    }
    return -1;
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif
    scanf("%lld%lld",&n,&k);
    while(n--)
    {
        scanf("%lld%lld%lld",&y,&z,&p);
        switch(k)
        {
            case 1:printf("%lld\n",power(y,z,p));break;
            case 2:
                if(z%p==0){puts("0");break;}
                else if(y%p==0){puts("Orz, I cannot find x!");break;}
                printf("%lld\n",((z%p)*power(y,p-2,p))%p);break;
            case 3:
                ans=bsgs(y,z,p);
                if(~ans) printf("%lld\n",ans);
                else puts("Orz, I cannot find x!");
                break;
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值