[bzoj3283]运算器

题目大意

数论题三合一。
第一问求 yz%p
第二问求 yx%p=z 的最小非负整数x
第三问求 Cyz%p

第一问

谁都会做

第二问

大步小步法。
假设解决这样一个问题
axb(modp)
首先考虑a与p互质的情况。那么显然是有循环节的。只需要考虑p以内。
设x= ApB
aApBb(modp)
aApbaB(modp)
A和B都在根号p范围内。
因此可以先枚举B把得到的值扔进哈希表。
然后枚举A在哈希表里检验,注意多个相同取最大的B。
A从1枚举,一开始判掉答案为0即可。
然而这题a与p并不互质。
考虑转化为a与p互质。
如果设 d=(a,p)
aax1b(modp)
adax1bd(modp)
此时a/d与p/d显然互质,那么有逆元存在。
于是可以继续递归处理,将返回的答案加1。
然而每次减一会不会出现负数?
所以一开始枚举x比较小的情况。
每次p至少除以2,只有log层。
无解判断?
每次b不是d的倍数,或者在最后一层找不到答案。

第三问

具体见组合数取模

#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=500000+10,maxha=1000000,maxw=40;
int ha[maxha+10][2],c[maxha+10];
bool bz[maxha+10];
int pri[maxn],cs[maxn],fac[maxn];
int i,j,k,l,t,n,m,y,z,p,pp,xx,yy,ca,ans,top,tot,cnt,lim;
int qsm(int x,int y,int p){
    if (!y) return 1;
    int t=qsm(x,y/2,p);
    t=(ll)t*t%p;
    if (y%2) t=(ll)t*x%p;
    return t;
}
int exgcd(int a,int b){
    if (!b){
        xx=1;yy=0;
        return a;
    }
    int d=exgcd(b,a%b);
    int x=yy,y=xx-(a/b)*yy;
    xx=x;yy=y;
    return d;
}
int getny(int a,int p){
    exgcd(a,p);
    xx=(xx%p+p)%p;
    return xx;
}
int hash(int x){
    int k=x%maxha;
    while (ha[k][0]!=-1&&ha[k][0]!=x) k=(k+1)%maxha;
    return k;
}
int bsgs(int a,int b,int p){
    if (b==1) return 0;
    int d=exgcd(a,p);
    if (d>1){
        if (b%d!=0) return -1000000;
        b/=d;p/=d;
        b=(ll)b*getny(a/d,p)%p;
        return bsgs(a,b,p)+1;
    }
    int i,k,t;
    lim=floor(sqrt(p))+1;
    fo(i,0,lim-1){
        t=(ll)b*qsm(a,i,p)%p;
        k=hash(t);
        if (!bz[k]){
            bz[k]=1;
            c[++top]=k;
        }
        ha[k][0]=t;
        ha[k][1]=i;
    }
    fo(i,1,lim){
        t=qsm(a,i*lim,p);
        k=hash(t);
        if (ha[k][0]==-1) continue;
        return i*lim-ha[k][1];
    }
    return -1000000;
}
void prepare(int p,int pp){
    int i,k,t;
    fac[0]=1;
    fo(i,1,p-1){
        k=(i%pp==0)?1:i;
        fac[i]=(ll)fac[i-1]*k%p;
    }
}
int calc(int n,int p,int pp,int f){
    if (n<pp) return fac[n];
    int t=1;
    t=qsm(fac[p-1],n/p,p);
    t=(ll)t*fac[n%p]%p;
    //tot+=f*num[n%p];
    tot+=f*n/pp;
    //tot+=f*num[p]*n/p;
    return (ll)t*calc(n/pp,p,pp,f)%p;
}
int C(int n,int m,int p){
    cnt=0;
    int i,k=p,l,t=floor(sqrt(p));
    fo(i,2,t)
        if (k%i==0){
            pri[++cnt]=i;
            cs[cnt]=0;
            while (k%i==0){
                cs[cnt]++;
                k/=i;
            }
        }
    if (k>1){
        pri[++cnt]=k;
        cs[cnt]=1;
    }
    l=0;
    fo(i,1,cnt){
        tot=0;
        t=qsm(pri[i],cs[i],1000000007);
        prepare(t,pri[i]);
        k=calc(n,t,pri[i],1);
        k=(ll)k*getny(calc(m,t,pri[i],-1),t)%t;
        k=(ll)k*getny(calc(n-m,t,pri[i],-1),t)%t;
        k=(ll)k*qsm(pri[i],tot,t)%t;
        l=(l+(ll)p/t*getny(p/t,t)%p*k%p)%p;
    }
    return l;
}
int main(){
    //freopen("3283.in","r",stdin);freopen("3283.out","w",stdout);
    fo(i,0,maxha) ha[i][0]=-1;
    scanf("%d",&ca);
    while (ca--){
        fo(i,1,top) ha[c[i]][0]=-1,bz[c[i]]=0;
        top=0;
        scanf("%d%d%d%d",&t,&y,&z,&p);
        if (t==1) ans=qsm(y,z,p);
        else if (t==2){
            fo(i,0,maxw+1)
                if (i>maxw||qsm(y,i,p)==z) break;
            if (i<=maxw) ans=i;else ans=bsgs(y,z,p);
        }
        else if (t==3) ans=C(z,y,p);
        if (t==2&&ans<0) printf("Math Error\n");/*printf("-1\n");*/
        else printf("%d\n",ans);
        //printf("%d\n",qsm(y,ans,p));
    }
    fclose(stdin);fclose(stdout);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值