离散对数及其拓展 大步小步算法 BSGS
离散对数及其拓展
离散对数是在群Zp∗Z_{p}^{*}Zp∗而言的,其中ppp是素数。即在在群Zp∗Z_{p}^{*}Zp∗内,aaa是生成元,求关于xxx的方程ax=ba^x=bax=b的解,并将解记作x=logabx=log_{a}{b}x=logab,离散对数指的就是这个logablog_{a}{b}logab.由于群Zp∗Z_{p}^{*}Zp∗的阶是p−1p-1p−1,且是循环群,因为生成元的阶是p−1p-1p−1,因而模p−1p-1p−1相等的指数可以看做一样的数,x=logabx=log_{a}{b}x=logab的值不妨定为Zp−1Z_{p-1}Zp−1的元素,即模p−1p-1p−1的数,即0,1,2,3,…,p−20,1,2,3,\ldots,p-20,1,2,3,…,p−2.
以上说的是数学上对离散对数的定义,但是,搞算法竞赛人说的离散对数和这个有点差异,差异在于没有要求aaa是生成元。
数学上的离散对数的定义,解一定存在且解模p−1p-1p−1意义下唯一,而我们平常说的离散对数由于没有要求aaa是生成元,那么解可能不存在,解模p−1p-1p−1意义下不唯一,例如群Z7∗Z_7^*Z7∗中222不是生成元(因为23=12^3=123=1),于是以222为底的离散对数,模666意义下解不唯一,模333意义下解才唯一;而且当b=3,5,6b=3,5,6b=3,5,6的时候没有解。
所以算法竞赛中说的离散对数一般是任意的aaa,若有解求最小非负整数解,否则返回无解。
如何求离散对数的值?由于答案在Zp−1Z_{p-1}Zp−1内,最简单暴力的做法是枚举Zp−1Z_{p-1}Zp−1中的每个数,共p−1p-1p−1个数,复杂度O(p)O(p)O(p).
更高效的做法------Shank的大步小步算法
注意到Zp∗Z_{p}^{*}Zp∗是个群,所以会有很多很好的性质,例如每个元素都有逆元。
我们选取一个数s,然后任意指数xxx都可以表示成x=ks+r(0≤r<s)x=ks+r(0 \leq r < s)x=ks+r(0≤r<s).在群内,有------
ax=baks+r=b两边乘a−ksar=a−ksbar=(a−s)kb\begin{aligned}
a^x &= b \\
a^{ks+r} &= b \quad\quad\quad\quad\quad \text{两边乘}a^{-ks}\\
a^r&=a^{-ks}b\\
a^r&= \left(a^{-s}\right)^{k}b
\end{aligned}axaks+rarar=b=b两边乘a−ks=a−ksb=(a−s)kb
每一个指数xxx都可以和一个有序对(k,r)(k,r)(k,r)建立一一对应关系,求xxx就变成确定(k,r)(k,r)(k,r)的问题了。注意上面的式子最后一行,左边ar(0≤r<k)a^r(0 \leq r < k)ar(0≤r<k)最多有sss个取值,右边a−ka^{-k}a−k是固定的,而0≤k≤p−2s0 \leq k \leq \frac{p-2}{s}0≤k≤sp−2,因此右边最多有t=1+⌈p−2s⌉t=1+\lceil \frac{p-2}{s}\rceilt=1+⌈sp−2⌉个取值。
于是变成了右边ttt个数在左边sss个数中有没有相等的元素.
这便是Shank的大步小步算法(Shank’s Baby-Step-Giant-Step
Algorithm)。左边的rrr指数是每一加1,是小步;右边指数每次的变化是sss,是大步。
为了求最小的xxx,具体地我们要从小到大枚举kkk,计算出右边的值,查询左边sss个数中是否有相等的数。若有,符合条件最小的rrr与kkk即构成最小的解xxx;若无,则看下一个kkk;若所有kkk都看完了还没有,那么就说明无解。
为了加速查询,显然对左边的sss个数需要预先计算出来,并建立由值查询最小rrr的索引"数组",如果直接用值做下标的话,空间需要O(p)O(p)O(p),但是总共才sss个值,所以应该用map建立"数组"或者hash(这个写麻烦一点)。
计算左边和右边所有取值的总的复杂度是O(s+t)O(s+t)O(s+t).插入及查询的时间复杂度使用map是O((s+t)lnr)O((s+t)\ln{r})O((s+t)lnr),使用hash是O(s+t)O(s+t)O(s+t)。空间复杂度是索引数组大小O(s)O(s)O(s)。而t=1+⌈p−2s⌉t=1+\lceil \frac{p-2}{s}\rceilt=1+⌈sp−2⌉,取s=ps=\sqrt{p}s=p附近的数,可以获得比较好的复杂度,如果使用map的话就是O(plnp)O(\sqrt{p}\ln{p})O(plnp),如果是使用hash的话就是O(p)O(\sqrt{p})O(p).
如果map会被卡就换成hash吧。
大步小步思路
略微思考便可以发现大步小步算法的精妙在于将原本需要的nnn次计算的问题变成了左右各O(n)O(\sqrt{n})O(n)次的计算加O(n)O(\sqrt{n})O(n)次插入和查询问题。**这可以说是一种牺牲空间换时间的一种思路。**当我们去掉a,ba,ba,b在这里代表的数含义的话,如果还可以有相同的变换,那么也可以如此降低复杂度。
另外,一点小优化,x=ks+rx=ks+rx=ks+r也可以换成x=ks−r(k≥1,1≤r≤s)x=ks-r \quad(k \geq 1,1 \leq r \leq s)x=ks−r(k≥1,1≤r≤s)的形式,然后将式子变换成(as)k=bar(a^s)^k=ba^r(as)k=bar.如此,就不用求逆元了。
会发现上面式子的变换只用到了群的性质,所以Zp∗Z_p^*Zp∗换成Zn∗Z_n^*Zn∗依旧可以如此做,只是阶不是由p−1p-1p−1变成了n−1n-1n−1,而是变成了ϕ(n)\phi(n)ϕ(n),事实上p−1p-1p−1只是ppp是质数时的ϕ(p)\phi(p)ϕ(p)。当然,指数算到ϕ(n)−1\phi(n)-1ϕ(n)−1即可,当然,算到n−2n-2n−2也无妨,只是多算了一点而已,不影响结果。
当然,这就要求aaa必须是Zn∗Z_n^*Zn∗中的元素了,即(a,n)=1(a,n)=1(a,n)=1,即互质。
拓展离散对数的思考与推导
离散对数的拓展,其实是想解ax≡b(modn)a^x \equiv b \pmod{n}ax≡b(modn)的方程中最小的自然数解x,其中aaa是一般数,不保证与nnn互质。因此必须思考aaa与nnn不互质如何解的问题。
基本思路是,通过方程的等价变化,将暂时无法解决的问题化为可以解决的问题。
[如果只想看结论可以直接往下看下一小节的具体算法部分。]{.underline}
不妨考虑成qax≡b(modn)qa^x \equiv b \pmod{n}qax≡b(modn)形式的问题,首先将其视作关于axa^xax的线性同余方程,线性同余方程有解等价于(q,n)∣b(q,n) \mid b(q,n)∣b.显然关于axa^xax的线性同余方程有解是原方程有解的必要条件,此必要条件成立时,qax≡b(modn)qa^x \equiv b \pmod{n}qax≡b(modn)显然等价于qa(a,x)ax−1≡b(a,n)(modn(a,n))\frac{qa}{(a,x)}a^{x-1} \equiv \frac{b}{(a,n)} \pmod{\frac{n}{(a,n)}}(a,x)qaax−1≡(a,n)b(mod(a,n)n)。新的方程和原方程形式上相同,但是模数变小了。如此,只要aaa与模数不互质,就重复进行如此的变换,最终一定会终止于互质的情况(当然如果某一步等价变换的必要条件不满足则直接可以得出无解结论而提前终止了)。
假设最后终止于qcax−c≡bc(modnc)q_ca^{x-c} \equiv b_c \pmod{n_c}qcax−c≡bc(modnc),aaa与nnn已经互质,则大步小步算法解之即可。
当指数xxx在整个整数上域取值,这一系列的变换显然等价。
拓展离散对数的具体算法
解方程ax≡b(modn)a^x \equiv b \pmod{n}ax≡b(modn)的最小自然数解。
方程等价变换
将原方程通过等价变换,始终保持着qian−i≡bi(modni)q_{i}a^{n-i} \equiv b_i \pmod{n_i}qian−i≡bi(modni)的形式。
令q0=1,b0=b,n0=nq_0=1,b_0=b,n_0=nq0=1,b0=b,n0=n。
迭代过程,qi+1=qia(a,ni),bi+1=bi(a,ni),ni+1=ni(a,ni)q_{i+1}=q_i\frac{a}{(a,n_i)},b_{i+1}=\frac{b_i}{(a,n_i)},n_{i+1}=\frac{n_i}{(a,n_i)}qi+1=qi(a,ni)a,bi+1=(a,ni)bi,ni+1=(a,ni)ni,迭代的条件是bi+1=bi(a,ni)b_{i+1}=\frac{b_i}{(a,n_i)}bi+1=(a,ni)bi是整数。
迭代终止条件:
不满足迭代条件返回无解
(a,ni)=1(a,n_i)=1(a,ni)=1,终止迭代,假设终止时的iii取值是ccc.
对迭代终止时的方程qcan−c≡bc(modnc)q_ca^{n-c} \equiv b_c \pmod{n_c}qcan−c≡bc(modnc)解关于an−ca^{n-c}an−c的线性同余方程。
若线性同余方程无解,返回无解。
否则,假设解得an−c≡B(modN)a^{n-c} \equiv B \pmod{N}an−c≡B(modN)。
若(B,N)=1(B,N)=1(B,N)=1,则a,Ba,Ba,B都在群ZN∗Z_{N}^{*}ZN∗中,大步小步算法解ax≡acB(modN)a^x \equiv a^cB \pmod{N}ax≡acB(modN)的最小自然数解x,终止。
否则,返回无解。
当然,最后先解线性同余方程,再进一步求x不是必要的。为了减少代码量,也可以直接再迭代终止形式的方程的基础上将aca^cac调到右边,然后大步小步思想解决。事实上,大多数时候为了程序的简洁性都是如此做的。
Code
struct mod_sys{
/*other mod_sys code*/
// 这些方法和成员必须添加上
set_mod,to_std and mod are needed
// 群Z_{p}^{*}下的离散对数log_a{b} 预设p=mod是素数
// 使用大步小步算法
// a^x = b (mod p)
// 不要求a是生成元,但a mod p != 0,b mod p != 0
// 由于时间复杂度 p^0.5*ln(p) 空间复杂度p^0.5
// 故p^0.5肯定在int范围内,而且应该会更小一些
// 预设p^2不爆ll ,否则乘法需要换成quick_mlt版本
// 使用unordered_map作为查询数组
// 返回是否有解,有解的情况下,x储存最小非负整数解
bool log_p(ll a, ll b, ll &x) {
a = to_std(a); b = to_std(b);
unordered_map<ll,ll>val_to_r;
ll s = (ll)ceil(sqrt((long long)mod));
// x = ks-r r in [1,s] k in [1,(mod-2)/s+1]
// (a^s)^k=b*a^r
ll ar = 1,bar;
for (ll r = 1; r <= s; ++r) {
ar = (ar*a)%mod;
bar = (b*ar)%mod;
val_to_r[bar] = r; // 相同的k,查询应该返回最大的r,正序枚举r
}
// 循环结束,ar就是a^s
ll &as = ar;
ll ask = 1; // (a^s)^k
int t = (mod-2)/s+1;
for (int k = 1; k <= t; ++k) {
ask = (ask*as)%mod;
auto it = val_to_r.find(ask);
if (it != val_to_r.end()) {
x = k*s-it->second;
return true;
}
}
return false;
}
// n=mod>=1,无其它特殊限制
// (a,n)=1,(b,n)=1
// a^x \equiv b (%n)
// 返回是否有解
// x存储最小自然数解
bool log_n_easy(ll a,ll b, ll &x) {
// x=ks-r in [0,phi_mod)
// s \in [1,s], k in [1,(phi_mod-1)/s+1]
ll phi_mod = phi(mod);
a = to_std(a); b = to_std(b);
unordered_map<ll,ll>val_to_r;
ll s = (ll)ceil(sqrt((long long)phi_mod));
ll ar = 1,bar; // a^r, b*a^r
for (ll r = 1; r <= s; ++r) {
ar = (ar*a)%mod;
bar = (b*ar)%mod;
val_to_r[bar] = r; // 相同的k,查询应该返回最大的r,正序枚举r
}
// 循环结束,ar就是a^s
ll &as = ar;
ll ask = 1; // (a^s)^k
// ask = bar
int t = (phi_mod-1)/s+1;
for (int k = 1; k <= t; ++k) {
ask = (ask*as)%mod;
auto it = val_to_r.find(ask);
if (it != val_to_r.end()) {
x = k*s-it->second;
return true;
}
}
return false;
}
// a^x = b (%mod) a,b,mod>=1没有特殊地限制
// 返回是否有解,x存储最小自然数解
bool log_n(ll a,ll b, ll &x) {
ll q = 1,d,c=0;
ll &n = mod;
a = to_std(a); b = to_std(b);
while (true) {
if (q == b) return c;
d = __gcd(a,n);
if (1 == d) break;
if (b%d) return false;
b /= d; n /= d;
q = a/d*(q%n)%n;
a %= n;
++c;
}
// qa^{x-c} = b (%n) <===>
// 先解线性同余方程的步骤去掉,之后给log_n_easy添加一个参数q即可
ll B,N;
if (!linear_congruence_equation(q,b,n,B,N))
return false;
if (__gcd(B,N) != 1) return false;
mod = N;
bool t = log_n_easy(a,B,x);
x += c;
return t;
}
};
离散对数及其拓展 大步小步算法 BSGS的更多相关文章
- [模板]大步小步算法——BSGS算法
大步小步算法用于解决:已知A, B, C,求X使得 A^x = B (mod C) 成立. 我们令x = im - j | m = ceil(sqrt(C)), i = [1, m], j = [0, ...
- 离散对数&&大步小步算法及扩展
bsgs algorithm ax≡b(mod n) 大步小步算法,这个算法有一定的局限性,只有当gcd(a,m)=1时才可以用 原理 此处讨论n为素数的时候. ax≡b(mod n)(n为素数) 由 ...
- 【题解】Matrix BZOJ 4128 矩阵求逆 离散对数 大步小步算法
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4128 大水题一道 使用大步小步算法,把数字的运算换成矩阵的运算就好了 矩阵求逆?这么基础的线 ...
- 大步小步算法模板题, poj2417
大步小步模板 (hash稍微有一点麻烦, poj不支持C++11略坑) #include <iostream> #include <vector> #include <c ...
- [BSGS]大步小步算法
问题 BSGS被用于求解离散对数,即同余方程: \[ A^x\equiv B\pmod{P} \] 求\(x\)的最小非负整数解. 保证\(A\perp P\)(互质). 分析 首先,我们根据费马小定 ...
- BSGS算法(大步小步算法)
计算\(y^x ≡ z \ mod\ p\) 中 \(x\) 的解. 这个模板是最小化了\(x\) , 无解输出\(No \ Solution!\) map<ll,ll>data; ll ...
- UVA 11916 Emoogle Grid 离散对数 大步小步算法
LRJ白书上的题 #include <stdio.h> #include <iostream> #include <vector> #include <mat ...
- BSGS-Junior·大步小步算法
本文原载于:http://www.orchidany.cf/2019/02/06/BSGS-junior/#more \(\rm{0x01}\) \(\mathcal{Preface}\) \(\rm ...
- 洛谷 - P4861 - 按钮 - 扩展大步小步算法
https://www.luogu.org/problemnew/show/P4861 把好像把一开始b==1的特判去掉就可以AC了. #include<bits/stdc++.h> us ...
随机推荐
- CSS学习笔记--Div+Css布局(div+span以及盒模型)
1.DIV与SPAN 1.1简介 1.DIV和SPAN在整个HTML标记中,没有任何意义,他们的存在就是为了应用CSS样式 2.DIV和span的区别在与,span是内联元素,div是块级元素 内联元 ...
- Nutz框架-- Cnd条件使用原生sql
案例 今天接到一个临时的业务需求,做一个简单的过滤作为临时业务需要使用一两天,于是想到在原有的Cnd条件上加上一个Not like 进行过滤,但是发现现有Cnd条件查询好像满足不了 解决方案 使用Nu ...
- Spring相关jar说明
Spring整合使用说明 一.只是使用spring框架 dist\spring.jar lib\jakarta-commons\commons-logging.jar 如果使用到了切面编程(AOP), ...
- Springboot+SpringSecurity实现图片验证码登录问题
这个问题,网上找了好多,结果代码都不全,找了好多,要不是就自动注入的类注入不了,编译报错,要不异常捕获不了浪费好多时间,就觉得,框架不熟就不能随便用,全是坑,气死我了,最后改了两天.终于弄好啦; 问题 ...
- python笔记18(复习)
今日内容 复习 内容详细 1.Python入门 1.1 环境的搭建 mac系统上搭建python环境. 环境变量的作用:方便在命令行(终端)执行可执行程序,将可执行程序所在的目录添加到环境变量,那么以 ...
- 使用Java迭代器实现Python中的range
如果要想迭代一个类的对象,那么该类必须实现 Iterable 接口,然后通过 iterator 方法返回一个 Iterator 实例. Range 类实现了Python中的range的所有用法,如:r ...
- vue初始化、数据处理、组件传参、路由传参、全局定义CSS与JS、组件生命周期
目录 项目初始化 组件数据局部化处理 子组件 父组件 路由逻辑跳转 案例 组件传参 父传子 子组件 父组件 子传父 子组件 父组件 组件的生命周期钩子 路由传参 第一种 配置:router/index ...
- Windows系统-cmd中的tracert命令
大部分同学都是用的Linux系统来测试网络命令相关工具,我用Windows10系统来测试tracert. tracert:也被称为Windows路由跟踪实用程序,在命令提示符(cmd)中 ...
- BZOJ 3343 教主的魔法(分块)
题意: 有一个1e6的数组,t次操作:将[l,r]内的值增加w,或者查询[l,r]内的值大于等于add的 思路: 分块,块大小为sqrt(n),每次只需要暴力头尾两块,中间的整块打标记, 对于查询查操 ...
- 《N诺机试指南》(五)进制转化
进制转化类题目类型: 代码详解及注释解答: //进制转化问题 #include <bits/stdc++.h> using namespace std; int main(){ // 1 ...