bzoj2219 数论之神

题目链接:bzoj2219
题目大意:
对于给定的3个非负整数 A,B,K,求 xA=B(mod2K+1) 在[0,2K]范围内 x 的个数。
1<=A,B<=109,1<=K<=5108

题解:
rt..数论
大概涉及到中国剩余定理的推论,BSGS,原根,指标等知识。
这道题跟bzoj1319&bzoj1420最大的不同就是模数是个奇数(即不一定是质数)
那么我们考虑把它分解质因数: 2K+1=pt11pt22ptrr
因为有个中国剩余定理的推论(根本不知道为什么):

一个取模数互质的同余方程组(未必线性),组合之后,这个同余方程组的解的个数为各方程解的个数的乘积。
[大概意思就是从每个方程的解中选一个组合在一起,那么就有各方程解的个数的乘积种。但是它怎么能保证这样得到每个解都是互不相等的呢?求dalao告知]

所以我们只用处理 xA=B(modptii) 这样的方程的解的个数,相乘就是答案了。

以下直接写作 p t
因为可能出现B模上 pt 为0这种情况,所以讨论一下:

Bmodpt=0 时:
那么原方程就变为:

xA=0(modpt)

于是就要求 xA pt 的倍数。
xA=(pk)A=pkA ,那么即要求 kAt
于是最小的 k=t1A+1
由于解集为[0, pt )范围内的k的倍数,所以这个方程的解的数量就是 ptk ,即 pt(t1A+1)

Bmodpt0 时:
那么我们把B写成 B×pk 的形式
于是原方程就变为:

xA=B×pk(modpt)

显然,如果A不能整除k,那么这个方程是无解的。
然后我们把方程转化为:
xpkAA=B(modptk)

于是就跟 bzoj1319&bzoj1420一样了。
弄成指标得到一个形如 ax=b(modp) 的方程,我们又知道,求解模线性方程的话,当且仅当 gcd(a,p)b 时,解的个数为 gcd(a,n) 。于是我们判断无解的情况(答案为0),然后就可以根据这个得到该方程的解的个数了。
虽然得到答案了..但是会感觉有那么点奇怪。
因为 解的取值范围改变了!
原方程中 x 的取值范围是[0,pt),那么 xpkA 的范围就应该变成 [0,ptkA) 。可是转化之后, xpkA 的范围是 [0,ptk) ,所以我们要将结果乘上 pkkA 才是原方程在 [0,pt) 上解的个数。

注意此时不要把p-1当成 φ (模数)了啊,求原根求逆元的时候要注意啊。

参考题解:
http://blog.csdn.net/regina8023/article/details/44863519
http://blog.csdn.net/popoqqq/article/details/41595187
http://www.cnblogs.com/KonjakJuruo/p/5853816.html

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
#define maxn 51000

struct node
{
    LL x,id;
    bool operator < (const node y) const {return x<y.x;}
}hh[maxn];
LL qpow(LL x,LL t) {LL ret=1;while (t) {if (t&1) ret*=x;x*=x;t>>=1;}return ret;}
LL qpow(LL x,LL t,LL p)
{
    LL ret=1;
    while (t)
    {
        if (t&1) ret=(ret*x)%p;
        x=(x*x)%p; t>>=1;
    }return ret;
}
LL gcd(LL a,LL b){return (b==0)?a:gcd(b,a%b);}
LL twocut(LL r,LL k)
{
    LL l=0;
    while (l<=r)
    {
        LL mid=(l+r)>>1;
        if (hh[mid].x==k) return hh[mid].id;
        if (hh[mid].x<k) l=mid+1;
        else r=mid-1;
    }return -1;
}
LL BSGS(LL a,LL b,LL p,LL phi)//a^x=b (mod p)
{
    LL m=ceil(sqrt(p));
    LL ret=0,i,aj=1;
    for (i=0;i<m;i++)
    {
        hh[i].x=aj;
        hh[i].id=i;
        aj=(aj*a)%p;
    }sort(hh,hh+m);
    LL cnt=0;hh[cnt]=hh[0];
    for (i=1;i<m;i++) if (hh[i].x==hh[i-1].x) hh[++cnt]=hh[i];
    LL ai=b,am=qpow(aj,phi-1,p);
    for (i=0;i<m;i++)
    {
        LL w=twocut(cnt,ai);
        if (w!=-1) {ret=w+m*i;break;}
        ai=(ai*am)%p;
    }
    return ret;
}
LL cnt,pri[maxn];
void get_pri(LL p)
{
    cnt=0;
    for (LL i=2;i*i<=p;i++)
     if (p%i==0)
     {
         pri[++cnt]=i;
         while (p%i==0) p/=i;
     }
    if (p>1) pri[++cnt]=p;
}
LL get_r(LL p)//求原根
{
    get_pri(p-1);LL g=2;
    while (g<p)
    {
        bool bk=false;
        for (LL i=1;i<=cnt;i++)
         if (qpow(g,(p-1)/pri[i],p)==1) {bk=true;break;}
        if (!bk) break; g++;
    }return (g<p)?g:0;
}
LL solve(LL A,LL B,LL p,LL t)//x^A=B(mod p^t)
{
    LL pt=qpow(p,t);
    if (B%pt==0) return qpow(p,t-((t-1)/A+1));
    LL k=0,ret;
    while (B%p==0) k++,B/=p;
    if (k%A!=0) return 0;

    ret=qpow(p,k-k/A);t-=k;
    LL g=get_r(p);
    if (k!=0) pt=qpow(p,t);

    LL phi=pt-pt/p;//对于phi(p^k)有phi(p^k)=(p-1)p^(k-1)=p^k-p^(k-1)
    LL Ib,d=gcd(A,phi);
    Ib=BSGS(g,B,pt,phi);
    if (Ib%d!=0) return 0;
    ret*=d;return ret;
}
int main()
{
    //freopen("a.in","r",stdin);
    //freopen("a.out","w",stdout);
    int T;LL A,B,K,i,ans,t;
    scanf("%d",&T);
    while (T--)
    {
        scanf("%lld%lld%lld",&A,&B,&K);//x^A=B(mod 2K+1)
        ans=1;K=2*K+1;
        for (i=2;i*i<=K;i++)
         if (K%i==0)
         {
            t=0;
            while (K%i==0) t++,K/=i;
            ans*=solve(A,B,i,t);
            if (ans==0) break;
         } 
        if (K!=1) ans*=solve(A,B,K,1);
        printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值