discrete logarithm problem
Problem Description
You are given three integers p,a,b, where p is a prime number and p−1 only has prime factors 2 and/or 3. Please find the minimum positive integer x such that ax≡b(modp).
Input
The first line contains an integer T indicating there are T tests. Each test consists of a single line containing three integers: p,a,b.
-
T≤200
-
-
the prime factors of p−1 can only be 2 or 3
-
2≤a,b≤p−1
Output
For each test, output a line containing an integer x, representing the minimum positive value such that ax≡b(modp). If there didn’t exist any such number x, please output −1.
Sample Input
6
65537 2 3
65537 2 4
65537 3 4
65537 4 4
65537 5 4
666334875701477377 2 3
Sample Output
-1
2
45056
1
36864
1957714645490451
題意
已知,求解
題解
因爲,而離散對數求解算法BSGS的複雜度是。
賽後題解說是CTF的簡單算法, Pohlig Hellman。但是沒學過密碼學,等着看看再來補一發 Pohlig Hellman的。
補完了補完了,代碼在下面,Pohlig Hellman講解在這裏,水平有限,不一定講得清楚。
時間直接從1000+ms降到62ms,爽啊
回去重學了原根,然後反覆研究才弄明白大佬不明不白的三言兩語的題解。
該題做法是:
<1> 先求,爲的原根。原根是判斷所有,題中說了素因子只有2,3。所以判斷條件就成了,
<2> 求解中的(後面用)
<3> 求解中的。根據原根性質可以知道對於質數,其,所以我們可以將和建立一一對應關係。也就知道存在唯一的使得等式成立。
但是因爲,無法暴力尋找,也無法用離散對數求解。需要其他方法(下面)
<4> 因此式子由 轉換爲。因爲<3>我們又可以將其轉化爲,這是一個單變元模線性方程,可以用exgcd求解。因爲模數爲素數所以方程爲,無解輸出-1,有解即可求出最小正整數解x
大佬原話說(沒錯,大佬口頭說的題解就這麼長):
對於一個數x,先判斷其有沒有模p平方根,沒有的話就乘以原根g,再判斷有沒有四次方根,沒有的話乘以,再判斷有沒有八次方根,沒有的話就乘以。不斷循環,就能找到x乘以g的幾次方等於1,也就由知道了x等於g的幾次方。
我的理解是:(表達能力實在有限。。。)
本題因爲p-1只有素因子2,3,,所以只需要判斷。
對於,如果它沒有平方根,就代表k爲奇數,此時給兩邊乘上一個g,就成了g的偶數次,也就存在了平方根,存在平方根後,判斷存不存在四次方根,如果不存在就代表,所以乘以也就存在了四次方根。後面依次繼續八次方根。。。其他次方根同理,但是判斷次數及判斷條件會發生變化,例如:三次方根不存在則k可能會是1,2,而乘一次是k+1,所以三次方根需要多判斷一次,舉一個本題的例子說:對於,我們按順序判斷完其存在八次方根後,判斷其存不存在24次方根,不存在則(我只枚舉了最小的情況),此時我們給其乘以,不能一次保證存在24次方根,所以需要再判斷一次。
該題因此可以通過從判斷,可以確認x是g的幾次方。
也就求出了<3>中需要的。需要注意的是全程可能兩數相乘爆long long,需要快速乘
代碼
#include <bits/stdc++.h>
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <cmath>
#include <string>
#include <cstring>
#include <map>
#include <set>
#include <vector>
#include <cmath>
#include <ctime>
using namespace std;
#define me(x,y) memset(x,y,sizeof x)
#define MIN(x,y) (x) < (y) ? (x) : (y)
#define MAX(x,y) (x) > (y) ? (x) : (y)
#define SGN(x) ((x)>0?1:((x)<0?-1:0))
#define ABS(x) ((x)>0?(x):-(x))
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int maxn = 1e6;
const ll INF = 0x3f3f3f3f;
const int MOD = 1e9+7;
const int eps = 1e-8;
ll kk,tt;
ll qmul(ll a,ll b,ll p){
a%=p,b%=p;
ll ans=0;
while(b){
if(b&1) ans=(ans+a)%p;
a=(a+a)%p;
b>>=1;
}
return ans%p;
}
ll qpow(ll a,ll b,ll p){
ll ans=1;
while(b){
if(b&1) ans=qmul(ans,a,p);
a=qmul(a,a,p);
b>>=1;
}
return ans;
}
bool judge(ll x,ll p){
if(qpow(x,(p-1)/2,p)==1) return 0;
if(qpow(x,(p-1)/3,p)==1) return 0;
return 1;
}
void p_power(ll p){ //p-1=2^kk*3^tt
kk=0,tt=0;
ll pp = p-1;
while(pp%2 == 0){
kk++;
pp /= 2;
}
while(pp%3 == 0){
tt++;
pp /= 3;
}
}
ll get_power(ll x,ll g,ll p){
ll pp = p-1;
ll ans=0;
ll rec=g,po=1; //rec = g ^ po
for(int i = 0; i < kk; ++i){
pp /= 2;
if(qpow(x,pp,p) != 1){
ans += po;
x = qmul(x,rec,p);
}
rec = qmul(rec,rec,p);
po = po*2;
}
for(int i = 0; i < tt; ++i){
pp /= 3;
if(qpow(x,pp,p) != 1){
ans += po;
x = qmul(x,rec,p);
if(qpow(x,pp,p) != 1){
ans += po;
x = qmul(x,rec,p);
}
}
rec = qmul(rec,qmul(rec,rec,p),p);
po = po*3;
}
return p-1-ans;
}
ll exgcd(ll a,ll b,ll &x,ll &y){
if(b == 0){
x=1,y=0;
return a;
}
else{
ll d= exgcd(b,a%b,y,x);
y-=x*(a/b);
return d;
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("1in.in","r",stdin);
freopen("1out.out","w",stdout);
#endif
int t;cin>>t;
while(t--){
ll g,p,a,b;
scanf("%lld%lld%lld",&p,&a,&b);
for(int i= 2;;i++){ //求p的原根g
if(judge(i,p)){
g=i;break;
}
}
// cout<<"g:"<<g<<endl;
p_power(p); //p-1 = 2^k * 3^t
a=get_power(a,g,p);
b=get_power(b,g,p);
// cout<<"a:"<<a<<" "<<"b:"<<b<<endl;
if(a == 0 && b == 0) printf("1\n");
else if(a == 0) printf("-1\n");
else{
ll x,y;
ll d = exgcd(a,p-1,x,y); // ax=b (mod p)-> ax+(p-1)y = b
if(b%d) printf("-1\n");
else{
ll s = (p-1)/d;
if(x < 0) x += s;
x = qmul(x,b/d,s);
printf("%lld\n",x);
}
}
}
}
Pohlig Hellman:
#include <bits/stdc++.h>
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <cmath>
#include <string>
#include <cstring>
#include <map>
#include <set>
#include <vector>
#include <cmath>
#include <ctime>
using namespace std;
#define me(x,y) memset(x,y,sizeof x)
#define MIN(x,y) (x) < (y) ? (x) : (y)
#define MAX(x,y) (x) > (y) ? (x) : (y)
#define SGN(x) ((x)>0?1:((x)<0?-1:0))
#define ABS(x) ((x)>0?(x):-(x))
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e5+10;
const ll INF = 0x3f3f3f3f;
const int MOD = 1e9+7;
const int eps = 1e-8;
ll qmul(ll a,ll b,ll p){
return (a*b-(ll)((long double)a*b/p)*p+p)%p;
}
ll qpow(ll a,ll b,ll p){
ll ans=1;
while(b){
if(b&1) ans = qmul(ans,a,p);
a = qmul(a,a,p);
b >>= 1;
}
return ans;
}
const int S=5;
//以a爲基,n-1=x*2^t a^(n-1)=1(mod n) 驗證n是不是合數
//一定是合數返回true,不一定返回false
bool check(long long a,long long n,long long x,long long t)
{
long long ret=qpow(a,x,n);
long long last=ret;
for(int i=1;i<=t;i++)
{
ret=qmul(ret,ret,n);
if(ret==1&&last!=1&&last!=n-1) return true;//合數
last=ret;
}
if(ret!=1) return true;
return false;
}
// Miller_Rabin()算法素數判定
//是素數返回true.(可能是僞素數,但概率極小)
//合數返回false;
bool Miller_Rabin(long long n)
{
if(n<2)return false;
if(n==2)return true;
if((n&1)==0) return false;//偶數
long long x=n-1;
long long t=0;
while((x&1)==0){x>>=1;t++;}
for(int i=0;i<S;i++)
{
long long a=rand()%(n-1)+1;
if(check(a,n,x,t))
return false;//合數
}
return true;
}
long long factor[100];//質因數分解結果(剛返回時是無序的)
int tol;//質因數的個數。數組小標從0開始
long long gcd(long long a,long long b)
{
if(a==0)return 1;
if(a<0) return gcd(-a,b);
while(b)
{
long long t=a%b;
a=b;
b=t;
}
return a;
}
long long Pollard_rho(long long x,long long c)
{
long long i=1,k=2;
long long x0=rand()%x;
long long y=x0;
while(1)
{
i++;
x0=(qmul(x0,x0,x)+c)%x;
long long d=gcd(y-x0,x);
if(d!=1&&d!=x) return d;
if(y==x0) return x;
if(i==k){y=x0;k+=k;}
}
}
//對n進行素因子分解
map<ll,ll> mp;
void findfac(long long n)
{
if(Miller_Rabin(n))//素數
{
factor[tol++]=n;
mp[n]++;
return;
}
long long p=n;
while(p>=n) p=Pollard_rho(p,rand()%(n-1)+1);
findfac(p);
findfac(n/p);
}
ll get_n(ll x,ll a,ll p){
tol = 0;
mp.clear();
findfac(x);
for(int i= 0; i < tol; ++i){
while(x%factor[i] == 0 && qpow(a,x/factor[i],p) == 1)
x /= factor[i];
}
mp.clear();
findfac(x);
return x;
}
void ex_gcd(ll a,ll b,ll &d,ll &x,ll &y){
if(!b){
d = a;x = 1;y = 0;return;
}
ex_gcd(b,a%b,d,y,x);
y -= x*(a/b);
}
ll crt(int n,ll *a,ll *m){ // x%m[i]=a[i]
ll M = 1,ret = 0;
ll x,y,d;
for(int i = 1; i <= n; ++i) M *= m[i];
for(int i = 1; i <= n; ++i){
ll Mi = M / m[i];
ex_gcd(Mi,m[i],d,x,y);
x = (x%m[i]+m[i])%m[i];
ret = (ret+qmul(qmul(a[i],Mi,M),x,M))%M;
}
return (ret+M)%M;
}
map<ll,ll> hs;
ll get_c(ll b,ll p,ll pi){
for(int i = 0; i < pi; ++i){
if(b == hs[i]) return i;
}
return -1;
}
ll xx[maxn],pp[maxn];
ll Pohlig_Hellman(ll a,ll b,ll p){
ll n = get_n(p-1,a,p); //a對p的階
ll x,y,inv = qpow(a,p-2,p),col = 0;
for(auto it : mp){ //枚舉素因子
hs.clear();
int pi = it.first,count = it.second;
ll tmp = 1,cnt = qpow(a,n/pi,p);
ll bb = b,an = 0;
for(int i = 0; i < pi; ++i){ //哈希存儲a^(n/pi),a^(2n/pi),a^(3n/pi)...
hs[i] = tmp;
// cout<<"hs["<<i<<"]:"<<hs[i]<<endl;
tmp = qmul(cnt,tmp,p);
}
ll now = n;
for(int i = 1; i <= count; ++i){
now = n/qpow(pi,i,p); // n / (pi^i)
ll res = qpow(bb,now,p),c;
c = get_c(res,p,pi);
// cout<<"c: "<<c<<" ";
if(c == -1) return -1;
ll rel = c*qpow(pi,i-1,p);
an += rel;
ll inva = qpow(qpow(a,rel,p),p-2,p);
// cout<<"inva:"<<inva<<" ";
bb = qmul(bb,inva,p);
// cout<<"bb: "<<bb<<endl;
}
// cout<<endl;
xx[++col] = an;
pp[col] = qpow(pi,count,p);
// cout<<"xx: " <<xx[col]<<" pp:"<<pp[col]<<endl;
}
if(col == 0) return -1;
return crt(col,xx,pp);
}
int main(){
ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--){
ll p,a,b;
cin>>p>>a>>b;
ll ans = Pohlig_Hellman(a,b,p);
if(ans == -1) cout<<-1<<endl;
else cout<<ans<<endl;
}
return 0;
}