题意:
求方程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; }
原文地址:http://blog.csdn.net/ww140142/article/details/47814003