基于值域预处理的快速离散对数
概要
记给定模数 \(P\) 的给定原根 \(g\) 意义下,\(x\) 的离散对数为 \(\log_g x\)。
本文将介绍如何在 \(O(B+\frac{P\pi (\sqrt P)}{B})/O(\log x)\) 的时间复杂度内在线求解 \(\log_g x\)。
求解 \(\sqrt P\) 以内的离散对数
首先,暴力求解离散对数的方法是 BSGS,原理大概就是光速幂反过来,它的时间复杂度是 \(O(B)/O(\frac PB)\) 的。
然而对于一个数 \(c=ab\),有 \(\log_g c=\log_g a+\log_g b\)。
所以我们只需要知道所有质数点值的离散对数,然后线性筛即可。
这部分时间复杂度 \(O(B+\frac{P\pi (\sqrt P)}{B})\),当 \(B=\sqrt{P\pi (\sqrt P)}\) 时最优。
利用 \(\sqrt P\) 以内的离散对数求解较大的 \(x\)
令 \(P=ax+b\)。
-
由 \(P=ax+b\) 可以得出:\(\log_g x=\log_g(\frac{P-b}a)=\log_g(-b)-\log_g a=\log_g(P-1)+\log_g b-\log_g a\)。
-
由 \(P=(a+1)x-(x-b)\) 可以得出:\(\log_g x=\log_g(\frac{P+x-b}{a+1})=\log_g(x-b)-\log_g(a+1)\)。
对于 \(x \leq \sqrt P\) 的情况:由于已经预处理了,直接输出即可。
对于 \(x \gt \sqrt P\) 的情况:对于上面两个式子,\(\log_g(P-1)\) 是常量,并且一定有 \(a+1 \leq \sqrt P\),所以只需处理 \(\log_g b\) 或 \(\log_g(x-b)\)。而 \(\min \{b,x-b\} \leq \lfloor\frac x2\rfloor\),所以每次问题规模减半,时间复杂度 \(O(\log x)\)。
代码示例
// P11175
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace std;
#define int long long
const int N=4e4+9;
int pri[N],ntp[N],lg[N],pcnt,mod,g,sq,B;
int QPow(int x,int y){
int res=1;
while(y){
if(y&1) res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
#define Inv(x) QPow(x,mod-2)
#define cmod (mod-1)
namespace BSGS{
int invg,tmp;
__gnu_pbds::gp_hash_table<int,int> mp;
void Init(){
B=ceil(sqrt(sq*log(sq)));
invg=Inv(g),tmp=QPow(g,B);
for(int i=0,cur=1;i<=ceil(mod/B);i++,cur=cur*tmp%mod){
mp[cur]=i*B+1;
}
}
int Query(int x){
for(int i=0,cur=x;i<B;i++,cur=cur*invg%mod){
if(mp[cur]) return (i+mp[cur]-1)%mod;
}
return -1;
}
}
void InitP(int lim){
lg[1]=0;
BSGS::Init();
for(int i=2;i<=lim;i++){
if(!ntp[i]) pri[++pcnt]=i,lg[i]=BSGS::Query(i);
for(int j=1;j<=pcnt&&i*pri[j]<=lim;j++){
ntp[i*pri[j]]=1;
lg[i*pri[j]]=(lg[i]+lg[pri[j]])%cmod;
if(i%pri[j]==0) break ;
}
}
}
int neg1;
int F(int x){
if(x<=sq) return lg[x];
int v=mod/x,r=mod%x;
if(r<x-r) return (neg1+F(r)-lg[v]+cmod)%cmod;
else return (F(x-r)-lg[v+1]+cmod)%cmod;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
#define endl '\n'
cin>>mod>>g;
InitP(sq=ceil(sqrt(mod)));
neg1=BSGS::Query(mod-1);
int q;
cin>>q;
while(q--){
int x;
cin>>x;
cout<<F(x)<<endl;
}
return 0;
}

浙公网安备 33010602011771号