一、离散对数

给定 \(a,b,m\),存在一个 \(x\),使得

\(\displaystyle a^x\equiv b\pmod m\)

则称 \(x\) 为 \(b\) 在模 \(m\) 意义下以 \(a\) 为底的 离散对数

二、BSGS

离散对数:求解关于 \(x\) 的方程 \(a^x\equiv b\pmod m\)。

基本思想:(假设 \(\gcd(a,m)=1\),那么 \(a\) 在模 \(m\) 意义下存在逆元)

考虑类似分块的一个想法。首先设定一个常量 \(t\)。

设 \(x=qt+r\)(\(0\leq r<t\)),预处理所有 \(a^{qt}\) 模 \(m\) 的值,存到 Hash 表 / map 中。询问时,枚举 \(r\),因为 \(a^{qt+r}\equiv b\pmod m\Leftrightarrow a^{qt}\equiv b\cdot a^{-r}\pmod m\),所以判断是否存在 \(a^{qt}\equiv b\cdot a^{-r}\pmod m\) 即可。

预处理的复杂度为 \(\mathcal{O}(\frac{m}{t})\),单次询问的复杂度为 \(\mathcal{O}(t)\)。取 \(t=\sqrt{m}\),则复杂度为 \(\mathcal{O}(\sqrt{m})\)。

用 map 会多一个 \(\log\)。

不同的写法:如果不想求 \(a^{-r}\),设 \(x=qt-r\)(\(0\leq q,r<t\)), \(a^{qt-r}\equiv b\pmod m\Leftrightarrow a^{qt}\equiv b\cdot a^r\pmod m\),预处理 \(b\cdot a^r\) 模 \(m\) 的值,枚举 \(q\),判断是否能找到对应的 \(r\)。

//Luogu P3846
#include<bits/stdc++.h>
#define int long long
using namespace std;
map<int,int>mp;
int a,b,p,ans;
int mul(int x,int n,int mod){
int ans=mod!=1;
for(x%=mod;n;n>>=1,x=x*x%mod)
if(n&1) ans=ans*x%mod;
return ans;
}
int BSGS(int a,int b,int p){ //设 x=qt-r,预处理 b*(a^r)%p,枚举 q,判断是否存在 a^{qt}%p=b*{a^r}%p
a%=p,b%=p,mp.clear();
if(!a) return !b?0:-1;
int t=ceil(sqrt(p)),x=b;
for(int i=0;i<=t;i++) mp[x]=i,x=x*a%p; //枚举 r,预处理 b*(a^r)%p 的值
a=mul(a,t,p),x=a;
for(int i=1;i<=t;i++){ //枚举 q
if(mp.count(x)) return i*t-mp[x]; //判断是否能找到对应的 r。若找到了,则 x=qt-r 为 x 的一个解
x=x*a%p;
}
return -1;
}
signed main(){
scanf("%lld%lld%lld",&p,&a,&b);
ans=BSGS(a,b,p);
if(~ans) printf("%lld\n",ans);
else puts("no solution");
return 0;
}

二、exBSGS

求解关于 \(x\) 的方程 \(a^x\equiv b\pmod m\)。\(m\) 可取任意数。

BSGS:\(\gcd(a,m)=1\),\(a\) 在模 \(m\) 意义下存在逆元,可令等式右侧出现 \(a\) 的幂次。

扩展 BSGS:考虑 \(\gcd(a,m)\neq 1\) 的情况。一个想法是,将它转化为 \(\gcd(a,m)=1\)。

设 \(d=\gcd(a,m)\)。\(a^x\equiv b\pmod m\Leftrightarrow a^x+km=b\),根据裴蜀定理,方程有解当且仅当 \(\gcd(a,m)\mid b\)。所以,若 \(d\nmid b\),则原方程无解。

否则,令方程两边同时除以 \(d\),得:

\(\displaystyle\frac{a}{d}\cdot a^{x-1}+k\cdot\frac{m}{d}=\frac{b}{d}\)

此时,若 \(\gcd(a,m)\neq 1\),则令 \(x'=x-1,m'=\frac{m}{d},b'=\frac{b}{d}\),重复上述步骤,于是可以一直做下去,直到 \(\gcd(a,m')=1\)。

不妨设重复了 \(g\) 次,每次求得的 \(d=\gcd(a,m')\) 分别为 \(d_1,d_2,\cdots,d_g\)。记 \(\prod_{i=1}^g d_i =D\),原式就是:

\(\displaystyle\frac{a^g}{D}\cdot a^{x-g}\equiv \frac{b}{D}\pmod {\frac{m}{D}}\)

那么就令 \(a'=a,b'=\frac{b}{D},m'=\frac{m}{D}\),跑一遍 BSGS 即可(在枚举 \(a^{qt}\) 判断时乘上 \(\frac{a^g}{D}\),对应代码中的 ad。最后答案加上 \(g\) 就行了)。

//Luogu P4195
#include<bits/stdc++.h>
#define int long long
using namespace std;
map<int,int>mp;
int a,b,p,ans;
int mul(int x,int n,int mod){
int ans=mod!=1;
for(x%=mod;n;n>>=1,x=x*x%mod)
if(n&1) ans=ans*x%mod;
return ans;
}
int BSGS(int a,int b,int p,int ad){
a%=p,b%=p,mp.clear();
if(!a) return !b?0:-1;
int t=ceil(sqrt(p)),x=b;
for(int i=0;i<=t;i++) mp[x]=i,x=x*a%p;
a=mul(a,t,p),x=a*ad%p; //upd: 原来的 x=a 改为了 x=a*ad%p
for(int i=1;i<=t;i++){
if(mp.count(x)) return i*t-mp[x];
x=x*a%p;
}
return -1;
}
int exBSGS(int a,int b,int p){
a%=p,b%=p;
int g=0,ad=1,d,ans;
while((d=__gcd(a,p))!=1){
if(b%d) return -1;
g++,b/=d,p/=d,ad=(ad*a/d)%p;
if(ad==b) return g;
}
if(~(ans=BSGS(a,b,p,ad))) return ans+g;
return -1;
}
signed main(){
while(~scanf("%lld%lld%lld",&a,&p,&b)){
if(!a&&!b&&!p) break;
ans=exBSGS(a,b,p);
if(~ans) printf("%lld\n",ans);
else puts("No Solution");
}
return 0;
}

建议把 map 改成 Hash 表 QAQ。

「算法笔记」BSGS 与 exBSGS的更多相关文章

  1. 「算法笔记」快速数论变换(NTT)

    一.简介 前置知识:多项式乘法与 FFT. FFT 涉及大量 double 类型数据操作和 \(\sin,\cos\) 运算,会产生误差.快速数论变换(Number Theoretic Transfo ...

  2. 「算法笔记」树形 DP

    一.树形 DP 基础 又是一篇鸽了好久的文章--以下面这道题为例,介绍一下树形 DP 的一般过程. POJ 2342 Anniversary party 题目大意:有一家公司要举行一个聚会,一共有 \ ...

  3. 「算法笔记」2-SAT 问题

    一.定义 k-SAT(Satisfiability)问题的形式如下: 有 \(n\) 个 01 变量 \(x_1,x_2,\cdots,x_n\),另有 \(m\) 个变量取值需要满足的限制. 每个限 ...

  4. 「算法笔记」Polya 定理

    一.前置概念 接下来的这些定义摘自 置换群 - OI Wiki. 1. 群 若集合 \(s\neq \varnothing\) 和 \(S\) 上的运算 \(\cdot\) 构成的代数结构 \((S, ...

  5. 「算法笔记」状压 DP

    一.关于状压 dp 为了规避不确定性,我们将需要枚举的东西放入状态.当不确定性太多的时候,我们就需要将它们压进较少的维数内. 常见的状态: 天生二进制(开关.选与不选.是否出现--) 爆搜出状态,给它 ...

  6. 「算法笔记」旋转 Treap

    一.引入 随机数据中,BST 一次操作的期望复杂度为 \(\mathcal{O}(\log n)\). 然而,BST 很容易退化,例如在 BST 中一次插入一个有序序列,将会得到一条链,平均每次操作的 ...

  7. 「算法笔记」FHQ-Treap

    右转→https://www.cnblogs.com/mytqwqq/p/15057231.html 下面放个板子 (禁止莱莱白嫖板子) P3369 [模板]普通平衡树 #include<bit ...

  8. 「算法笔记」Min_25 筛

    戳 这里(加了密码).虽然写的可能还算清楚,但还是不公开了吧 QwQ. 真的想看的 私信可能会考虑给密码 qwq.就放个板子: //LOJ 6053 简单的函数 f(p^c)=p xor c #inc ...

  9. 「算法笔记」快速傅里叶变换(FFT)

    一.引入 首先,定义多项式的形式为 \(f(x)=\sum_{i=0}^n a_ix^i\),其中 \(a_i\) 为系数,\(n\) 为次数,这种表示方法称为"系数表示法",一个 ...

随机推荐

  1. Android数据存取

    Android数据存取 一.SharedPreferencesc存取数据 SharedPreferences是使用键值对的方式来存储数据的,也就是在保存一条数据时,需要给这条数据提供一个对应的键,这样 ...

  2. Mysql百万级数据索引重新排序

    参考https://blog.csdn.net/pengshuai007/article/details/86021689中思路解决自增id重排 方式一 alter table `table_name ...

  3. OpenStack之四: keystone验证服务(端口5000)

    #官网地址:https://docs.openstack.org/keystone/stein/install/keystone-install-rdo.html #:创建库,并授权 MariaDB ...

  4. Servlet(1):Servlet介绍

    一. Servlet介绍 Servlet 是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生 ...

  5. 手写Starter

    一. Starter工程的命名 Spring 官方定义的Starter通常命名遵循的格式为spring-boot-starter-{name},例如 spring-boot-starter-web.S ...

  6. C++STL标准库学习笔记(四)multiset续

    自定义排序规则的multiset用法 前言: 在这个笔记中,我把大多数代码都加了注释,我的一些想法和注解用蓝色字体标记了出来,重点和需要关注的地方用红色字体标记了出来,只不过这一次的笔记主要是我的补充 ...

  7. Mysql资料 存储索引

  8. 【web】docker复现环境踩坑

    在先知看到有师傅发了个学习 P 牛的代码审计的文章,在 github 上下下来复现环境,结果 docker 各种问题,气死 安装 docker-compose:pip install -i https ...

  9. Vlookup大叔与一对多查找(Excel函数集团)

    所谓一对多查找,就是根据一个条件,把多个符合条件的结果全部找出来. 其实吧,一对多查找不是什么,尤其是O365的Filter函数横空出世震撼全场之后,简直就是瞬间把所有传统的数组解法甩出去七八十来条街 ...

  10. 分组依据(Project)

    <Project2016 企业项目管理实践>张会斌 董方好 编著 [视图]选项卡下,[筛选器]楼下,住着个[分组依据]. 这个功能,说白了,就是指定个"组",把同一组的 ...