基于值域预处理的快速离散对数

概要

记给定模数 \(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;
}
posted @ 2025-03-10 21:04  JoeyJiang  阅读(50)  评论(0)    收藏  举报