bzoj 2219: 数论之神 (BSGS+GCD+数论)

7 篇文章 0 订阅

题目描述

传送门

题目大意:
XAB( mod 2K+1) ,在 [0,2k] 范围内解得个数。

题解

这道题用到的数论知识好多啊,还是一点点从头顺吧。

首先将 2k+1 分解质因数,得到质因子个同余方程

XAB( mod pdii)

根据中国剩余定理的推论,模 2k+1 意义下的解等于模 pdii 意义下解的乘积。

然后我们对于 XAB( mod pdii) A,B,pdii 中的关系进行分类讨论。
(1) B0( mod pd)
XA pd 的倍数,那么X中一定含有因子p。设p的指数是a,那么 aAd .
我们只要找到最小的a,那么 pa [0,pd) 范围内的所有倍数都是合法解。

a=d1A+1

所以解的个数就是 pdpa=pda

(2) gcd(B,pd)=1
所有奇素数以及他们的幂都有原根。
我们求出 pd 的原根,那么式子其实可以转化成

(gx)Agind(B)( mod pd),ind(B)gind(B)B( mod pd)

那么如何求原根呢?
原根:如果g是P的原根,那么g的 1...p1 次幂 mod p 的结果一定互不相同.
根据欧拉定理
aϕ(p)1( mod p),gcd(a,p)=1
ax1( mod p) ,当最小的x等于 ϕ(p) 时,a是p的原根。
那么我们是否需要将 2..ϕ(p) 中的数都判断一下呢,实际上只需要判断 ϕ(p) 的因子即可。如果因子中没有满足上式的,那么a就是p的原根。
一般原根的大小不会太大,所有我直接暴力枚举判断即可。
求出原根后,我们就得到了方程
Axinv(B)( mod ϕ(pd))

上面方程的解的个数就是原式的解的个数。
形如
AxC( mod m)
的方程,根据扩展欧几里得的推论可知,
t=gcd(A,m) ,如果t|C,那么方程解的个数为t,否则无解。
如何证明?扩展欧几里得定理,要求 gcd(A,m)=1 ,如果 gcd(A,m)=g,g1 ,那么需要同时除去g,所以要求 g|C .
x1,x2 为方程的两个解。
那么
Ax1Ax2( mod m)

A(x1x2)0( mod m)

(m/g)|(A/g)(x1x2) ,因为 m/g,A/g 互质,所以 (m/g)|(x1x2)
那么x其实可以表示成 x=x0+k(m/g),k[0,g1] ,所以解的个数为g。

(3) gcd(B,pd)1
虽然B是 pd 的倍数,但是B可能是p的倍数。
B=paq,gcd(p,q)=1
那么方程可以写成

XApaq( mod pd)

其中A|a,否则无解。因为如果A不是a的因数,那么 XA 中约数p的个数不可能和B中的相等。
所以可以设 a=kA ,那么x实际上可以写成 pky 的形式。
那么方程就变成了
payApaq( mod pd)

那么方程解的数量是否等于方程 yAq(modpda) 的数量呢?
其实是不对的,因为我们在化简的时候定义域的范围改变了,x的取值范围是 [0,pd) ,因为 x=ypa ,所以y的取值范围是 [0,pdk) ,而在第二个方程中y的取值范围变成了 [0,pda) ,所以原始方程的解的数量应该是 yAq( mod pda) 数量的 pak 倍。
第二个方程的求解方式同情况(2)

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#define LL long long
#define N 100003 
using namespace std;
map<LL,int> mp;
int pd[N],prime[N],n,f[N];
LL m[N],cnt[N];
void init(int n)
{
    for (int i=2;i<=n;i++) {
        if (!pd[i]) prime[++prime[0]]=i;
        for (int j=1;j<=prime[0];j++){
            if (prime[j]*i>n) break;
            pd[prime[j]*i]=1;
            if (i%prime[j]==0) break;
        }
    }
}
LL gcd(LL x,LL y)
{
    LL r;
    while (y) {
        r=x%y;
        x=y; y=r;
    }
    return x;
}
LL get_phi(LL x)
{
    LL ans=x;
    for (int i=2;i*i<=x;i++)
     if (x%i==0) {
        ans=(LL)ans/i*(i-1);
        while (x%i==0) x/=i;
     }
    if (x>1) ans=ans/x*(x-1);
    return ans;
}
LL quickpow(LL num,int x)
{
    LL base=num; LL ans=1;
    while (x) {
        if (x&1) ans=ans*base;
        x>>=1;
        base=base*base;
    }
    return ans;
}
LL quickpow(LL num,int x,LL p){
    LL base=num%p; LL ans=1;
    while (x) {
        if (x&1) ans=ans*base%p;
        x>>=1;
        base=base*base%p;
    }
    return ans;
}
LL calc(LL p,LL phi)
{
    int c=0;
    for (int i=2;i*i<=phi;i++)
        if (phi%i==0)
            f[++c]=i,f[++c]=phi/i;
    for (int g=2;;g++)
    {
        int j;
        for (j=1;j<=c;j++)
            if (quickpow(g,f[j],p)==1) break;
        if (j==c+1) return g;
    }
    return 0;
}
LL bsgs(LL a,LL b,LL p)  
{  
    LL m=ceil(sqrt(p)); LL ans=b; LL d=1;
    mp.clear();  
    LL tmp=quickpow(a,m,p);  
    mp[ans]=0;  
    for (LL i=1;i<=m;i++) ans=ans*a%p,mp[ans]=i;  
    for (int LL i=1;i<=m+1;i++) {  
        d=d*tmp%p;  
        if (mp[d]) {  
            return (LL)i*m-mp[d];  
        }  
    }  
    return -1;  
}  
LL solve(LL A,LL B,LL P,LL k)
{
    LL p=quickpow(P,k);
    if (B%p==0) {
        LL a=(k-1)/A+1;
        return quickpow(P,k-a);
    }
    LL t=0; LL opt=1; LL X=B;
    while (B%P==0) B/=P,t++;
    if (t%A) return 0;
    if (gcd(X,p)!=1) {
        p=quickpow(P,k-t);
        opt=quickpow(P,t-t/A);
    }
    LL phi=get_phi(p); LL g=calc(p,phi);
    LL C=bsgs(g,B,p);
    if (C==-1) return 0;
    t=gcd(A,phi);
    if (C%t==0) return (LL)t*opt;
    else return 0;
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("my.out","w",stdout);
    int T; scanf("%d",&T);
    init(100000);
    while (T--) {
       LL A,B,K;
       scanf("%lld%lld%lld",&A,&B,&K);
       LL p=2*K+1;
       n=0;
       for (int i=1;prime[i]*prime[i]<=p;i++)
        if (p%prime[i]==0) {
            m[++n]=prime[i]; cnt[n]=0;
            while (p%prime[i]==0) p/=prime[i],cnt[n]++;
        }
       if (p>1) m[++n]=p,cnt[n]=1;
       LL ans=1;
       for (int i=1;i<=n;i++)
        ans=ans*solve(A,B,m[i],cnt[i]);
       printf("%lld\n",ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值