题意:
求方程X^A = B(mod 2*K + 1)
X ∈[0, 2K] 内的解的个数;
题解:
一道数论的好题;
涉及知识点大概有:Crt推论,BSGS,EXGCD,原根与指标;
这道题的主要问题在于两点:
第一点:取模数不是质数,无法利用通常的方式解方程;
但是有中国剩余定理这个东西,定理的推论告诉我们:
一个取模数互质的同余方程组(未必线性),组合起来之后,这个同余方程解的个数为各方程解的个数的乘积;
(组合起来的方程的取模数为所有数的积;实际上这里解的范围都是属于[0 ,自己取模数) )
这点十分重要呢,它不仅证明了解的求法,而且如果有任意一个方程无解,那么整个就都是无解的;
解决了这一点之后,就是第二点:如何处理一个方程的解的个数;
暂且令当前方程为x^A =B (mod p^d);
因为中国剩余定理要求取模数互质,所以将2*K+1分解质因子作为所有方程;
然而我只会处理p^1的情况怎么办啊= =;
分类讨论:
p^d|B :这时p^d是B的因子,那么实际上就是x^A =0 (mod p^d)了;
如果设x=p^k*q,这里(p,q)=1;
k其实表示的是x中p因子的个数,那么在x^A中的个数就是k*A,也就有k*A>=d;
k>=d/A,也就是说k最小为ceil(d/A) (ceil为上取整函数);
因为x的取值范围是[0,p^d),而x=p^k*q ,显然d>k;
所以解的个数就是p^d/p^k=p^(d-k);
gcd(p^d,B)=p^k:这时二者之间有一个公约数;
直观的想,如果等式两边同时除一个p^k就解决啦;
但是要保证A|k,要不然的话x^A中约数p的个数就不可能和B的相等(显然);
当我们成功的把方程p^k*x'^A= B (mod p^d)
化简为 x'^A= B/(p^k) (mod p^(d-k))之后
我们就挂啦!
因为这样是不对的,确切的说是x'的取值范围变化了;
那么变化了多少呢?原来的范围是[0,p^(d-k/A)),而后变成了[0,p^(d-k));
原因就在于mod值的缩小,但是对于答案的影响和上面一样:
答案是第二个方程答案的p^(k-k/A)倍;
第二个方程怎么解? 取指标啊!
取完指标就是线性同余方程了,方程个数就是gcd(A,φ(p^d));
但是有个坑,同余方程可能无解啊。。
所以还要大步~小步~BSGS求出一个 以p^d的原根为底,关于B的,对p^d取模的 指标lnB (好TM绕);
然后判断gcd(A,φ(p^d))| lnB,如果不是因子则无解!
那么这题就结束了,可喜可贺;
什么gcd(B,p^d)=1没讨论?其实不就是上一段怎么解的事吗;
什么复杂度?我可是BZ这题Rank1 (倒数)的人啊;
【反正我咋看咋O(n);
反正我是线性筛搞了个素数表= =
然并卵,似乎复杂度没有本质改变?
代码:
#include<math.h>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 140142
using namespace std;
typedef long long ll;
const ll INF=0x7f7f7f7f7f7f7f7f7fll;
struct Hash_Set
{
ll head[N],next[N],X[N],val[N],tot;
void clear()
{
tot=0;
memset(head,0,sizeof(head));
memset(next,0,sizeof(next));
memset(val,-1,sizeof(val));
memset(X,0,sizeof(X));
}
ll& operator [](ll x)
{
ll index=x%N;
for(ll i=head[index];i;i=next[i])
{
if(X[i]==x)
return val[i];
}
next[++tot]=head[index];
head[index]=tot;
X[tot]=x;
return val[tot];
}
}hash;
ll pri[N],top,a[N],b[N];
bool vis[N];
void init()
{
for(ll i=2;i<N;i++)
{
if(!vis[i])
pri[++top]=i;
for(ll j=1;j<=top&&i*pri[j]<N;j++)
{
vis[i*pri[j]]=1;
if(i%pri[j]==0)
break;
}
}
}
ll pow(ll x,ll y,ll mod)
{
ll ret=1;
while(y)
{
if(y&1)
ret=ret*x%mod;
x=x*x%mod;
y>>=1;
}
return ret;
}
ll gcd(ll a,ll b)
{
ll t=a%b;
while(t)
{
a=b,b=t;
t=a%b;
}
return b;
}
void exgcd(ll a,ll b,ll &x,ll &y,ll &d)
{
if(!b)
x=1,y=0,d=a;
else
{
exgcd(b,a%b,y,x,d);
y-=a/b*x;
}
}
ll inv(ll X,ll mod)
{
ll x,y,d;
exgcd(X,mod,x,y,d);
return (x%mod+mod)%mod;
}
ll Root(ll x,ll phi)
{
ll st[N],top=0,temp=phi;
for(ll i=1,p=2;p*p<=temp;i++,p=pri[i])
{
if(temp%p==0)
{
st[++top]=phi/p;
while(temp%p==0)
temp/=p;
}
}
if(temp!=1)
st[++top]=phi/temp;
for(ll i=1,j;i<=phi;i++)
{
for(j=1;j<=top;j++)
{
if(pow(i,st[j],x)==1)
break;
}
if(j>top)
return i;
}
}
ll BSGS(ll A,ll B,ll C)
{
hash.clear();
ll bk=ceil(sqrt(C*1.0)),i,k,D,temp;
for(i=0,D=1;i<bk;i++,D=D*A%C)
{
if(hash[D]==-1)
hash[D]=i;
}
temp=inv(D,C);
for(i=0,k=B;i<=bk;i++,k=k*temp%C)
{
if(hash[k]!=-1)
return i*bk+hash[k];
}
return 0;
}
ll slove(ll A,ll B,ll C)
{
ll i,j,k,p,mod,lnB,g,d,cnt,ans;
for(i=1,p=2,ans=1;p*p<=C;i++,p=pri[i])
{
if(C%p==0)
{
mod=1,cnt=0;
while(C%p==0)
C/=p,mod*=p,cnt++;
if(B%mod==0)
{
d=pow(p,cnt-ceil(cnt*1.0/A),INF);
}
else
{
k=0;
while(B%p==0)
k++,B/=p;
if(k%A) d=0;
else
{
g=Root(mod,mod-mod/p);
lnB=BSGS(g,B,mod);
d=gcd(A,mod-mod/p);
if(lnB%d) d=0;
d*=pow(p,k-k/A,INF);
}
}
ans*=d;
}
}
if(C!=1)
{
mod=C,p=C,cnt=1;
if(B%mod==0)
{
d=pow(p,cnt-ceil(cnt*1.0/A),INF);
}
else
{
k=0;
while(B%p==0)
k++,B/=p;
if(k%A) d=0;
else
{
g=Root(mod,mod-mod/p);
lnB=BSGS(g,B,mod);
d=gcd(A,mod-mod/p);
if(lnB%d) d=0;
d*=pow(p,k-k/A,INF);
}
}
ans*=d;
}
return ans;
}
int main()
{
init();
ll c,T,A,B,C;
scanf("%lld",&T);
for(c=1;c<=T;c++)
{
scanf("%lld%lld%lld",&A,&B,&C);
printf("%lld\n",slove(A,B,C<<1|1));
}
return 0;
}