题意:给定一个数字sum,有 m 个询问:(xi, yi),求最小的非负整数 ki 满足 xi^ki =yi (mod p)。
其中 p 是 sum 的质因子。
1<=sum<=10^8,1<=m<=10^5,0<=xi, yi<=10^9
分析:
直接用离散对数求解K的复杂度为O( m log m ),m为根号n。对于该题每次询问都要单独计算,复杂度太高。
需要用到原根,离散对数,扩展欧几里得计算。
整体思路:
对sum分解质因数, 对于x^k mod p = y ,
先求出p的原根d,对同余式两边取对数得到 k*logd(x) mod p-1 = logd(y)
再使用扩展欧几里德求出上式中的k
复杂度优化!!:
令:A=logd(x) B=logd(y)
对同余式取对数时:即 求d^A mod p = x d^B mod p = y
baby step中要对d^m打表,由于对于同一质因子p,d是相同的。所以可以提前初始化表。
那么总复杂度变为O(m+p/m*q),p为质数大小,m为表长,q为查询次数
其中查询次数 q 为10^5,所以表要足够大,以使p/m*q这一项较小。
#include <cstdio> #include <iostream> #include <algorithm> #include <cstring> #include <string> #include <cmath> #include <vector> #include <map> #include <tr1/unordered_map> #define clr(x, y) memset(x, y, sizeof x) using namespace std; typedef long long LL; const double eps=1e-8; const int maxn=100100; const int mod=1e6+10; LL prime[maxn]; LL X[maxn],Y[maxn]; LL A[maxn],B[maxn]; LL ans[maxn]; LL M; //质因数分解 LL get_prime(LL n) { LL cnt=0; for(LL i=2;i*i<=n;i++) { if(n%i==0) { prime[cnt++]=i; while(n%i==0) n/=i; } } if(n!=1) prime[cnt++]=n; return cnt; } //快速幂 LL pow_mod(LL a,LL p,LL n) { LL ans=1; while(p) { if(p&1) ans=(ans*a)%n; p>>=1; a=(a*a)%n; } return ans; } //求原根 vector<LL>a; bool g_test(LL g,LL p) { for(LL i=0;i<a.size();i++) { if(pow_mod(g,(p-1)/a[i],p)==1) return 0; } return 1; } LL primitive_root(LL p) { LL tmp=p-1; a.clear(); for(LL i=2;i<=tmp/i;i++) { if(tmp%i==0) { a.push_back(i); while(tmp%i==0) tmp/=i; } } if(tmp!=1) a.push_back(tmp); LL g=1; while(true) { if(g_test(g,p)) return g; g++; } } //扩展欧几里得 void gcd(LL a,LL b,LL &d,LL &x,LL &y) { if(!b) { d=a; x=1; y=0;} else { gcd(b,a%b,d,y,x); y-=x*(a/b); } } //求逆元 LL inv(LL a,LL n) { LL d,x,y; gcd(a,n,d,x,y); return d==1 ? (x+n)%n : -1; } //手动哈希 struct HashTable { int top,head[mod]; struct Node { int x,y,next; }node[mod]; void init() { top=0; memset(head,0,sizeof(head)); } void insert(LL x,LL y) { node[top].x=x; node[top].y=y; node[top].next=head[x%mod]; head[x%mod]=top++; } LL query(LL x) { for(int tx=head[x%mod];tx;tx=node[tx].next) { if(node[tx].x==x) return node[tx].y; } return -1; } }hs; //求出所有查询中X,Y关于a(mod n)的离散对数 void log_mod(LL a,LL n) { LL m,v,e=1; m=mod; v=inv(pow_mod(a,m,n),n); //打表 a^0 ~ a^m-1, m为表长 hs.init(); for(int i=0;i<m;i++) { hs.insert(e,i); e=e*a%n; } //求出所有查询中X,Y关于a的离散对数 for(int j=0;j<M;j++) { //必须要取模后再计算 LL b=X[j]%n; for(int i=0;i<m;i++) { LL s=hs.query(b); if(s!=-1) { A[j]=i*m+s; break; } b=b*v%n; } b=Y[j]%n; for(int i=0;i<=n/m;i++) { LL s=hs.query(b); if(s!=-1) { B[j]=i*m+s; break; } b=b*v%n; } } } //用扩展欧几里得求a*x+b*y=c中x的最小非负整数解 LL get_K(LL a,LL b,LL c) { LL d,x,y; gcd(a,b,d,x,y); if(c%d!=0) return -1; x=x*c/d; b=b/d; x=x-(x/b*b); if(x<0) x+=b; return x; } int main() { int T; LL sum; scanf("%d",&T); for(int t=1;t<=T;t++) { memset(ans,-1,sizeof(ans)); scanf("%I64d %I64d",&sum,&M); printf("Case #%d:\n",t); for(int i=0;i<M;i++) { scanf("%I64d %I64d",&X[i],&Y[i]); } //质因数分解 LL cnt=get_prime(sum); LL temp; //枚举所有质因子 for(int j=0;j<cnt;j++) { //求该质因子的原根d LL d=primitive_root(prime[j]); //求所有的查询中X,Y关于d(mod prime[j])的离散对数 log_mod(d,prime[j]); for(int i=0;i<M;i++) { //求解最小的K, K*A[i]=B[i](mod (prime[j]-1)) temp=get_K(A[i],prime[j]-1,B[i]); if(temp!=-1) { if(ans[i]==-1) ans[i]=temp; else ans[i]=min(temp,ans[i]); } } } for(int i=0;i<M;i++) printf("%I64d\n",ans[i]); } return 0; }