一、离散对数

给定 \(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. 日常Java 2021/10/31

    泛型类 泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分.和迈型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开.一个泛型参数,也被称为一个类型变量, ...

  2. 零基础学习java------32---------css,javascript,jQuery

    一. CSS简单了解 需要掌握: 概念见day11中的课堂笔记 css:修饰html标签的样式 1.每个元素有一个style属性,其形式为:style="属性:值:属性:值...." ...

  3. oracle中的数组

    Oracle中的数组分为固定数组和可变数组. 一.固定数组固定数组:在定义的时候预定义了数组的大小,在初始化数组时如果超出这个大小,会提示ORA-06532:超出小标超出限制!语法:        T ...

  4. Windows zip版本安装MySQL

    Windows --MySQL zip版本安装记录: step1. 官网download zip包:http://cdn.mysql.com//Downloads/MySQL-5.7/mysql-5. ...

  5. 用户名、密码、整数等常用的js正则表达式

    1 用户名正则 //用户名正则,4到16位(字母,数字,下划线,减号) var uPattern = /^[a-zA-Z0-9_-]{4,16}$/; //输出 true console.log(uP ...

  6. Android 实现微信QQ分享以及第三方登录

    集成准备 在微信开放平台创建移动应用,输入应用的信息,包括移动应用名称,移动应用简介,移动应用图片信息,点击下一步,选择Android 应用,填写信息提交审核. 获取Appkey 集成[友盟+]SDK ...

  7. Redis集群的三种模式

    一.主从模式 通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据,因为持久化会把内存中数据保存到硬盘上,重启会从硬盘上加载数据. 但是由于数据是存储在一台服务器上的, ...

  8. Redis cluster 集群报错合集

    目录 一.连接集群操作报错(error)MOVED 二.集群关闭后重启报错 三.Redis (error) NOAUTH Authentication required 四.Redis集群使用中突然挂 ...

  9. pipeline 结构设计

    目录 一.pipeline步骤 二.案例 pipeline详解 只生成一次制品 不同环境部署 系统集成测试 指定版本部署 一.pipeline步骤 当团队开始设计第一个pipeline时,该如何下手呢 ...

  10. Python解释器下载安装

    一.简介 吉多·范罗苏姆(Guido van Rossum)在1989年的圣诞节期间,编写能够解释Python语言语法的解释器. 解释器版本 第一个数字是大版本号 数字不同功能上可能会有很大差异 py ...