第一篇\(Blog\)...
还是决定把\(luogu\)上的那篇搬过来了。
它可以用来求\(a^x \equiv b (mod \ n)\)这个同余方程的一个解,其中\(a,n\)互质。
欧拉定理告诉我们,这里\(a^{\varphi(n)} \equiv 1 (mod \ n)\)
由于\(a^0 \equiv 1 (mod \ n)\),所以这里\(x\)到\(\varphi(n)\)后\(a^x \ mod \ n\)就开始循环了。
所以我们最坏情况就是\(n\)为素数时,从\(0\)到\(n-1\)枚举\(x\)就行了。
这样我们就得到了一个\(O(n)\)复杂度的优秀算法。
然而\(n < 2^{31}\)......
我们考虑让\(x = im - j(0 \le j \le m)\),即把\(0...n-1\)这\(n\)个数按每块大小为\(m\)分块。
就有
\[
a^{im - j} \equiv b (mod \ n)
\]
两边同时乘\(a^j\)得
\[
a^{im} \equiv ba^j (mod \ n)
\]
对于等式右边,总共只会有\(m+1\)种不同的\(j\),我们把\(ba^0,ba^1,...,ba^m\)全塞到一个\(map\)里,\(i\)也只会有\(\lceil \frac{n}{m} \rceil\)种取值,直接暴力。
最后复杂度为\(O(m + \lceil\frac{n}{m} \rceil)\)
取\(m = \lceil \sqrt{n} \rceil\),就可以做到\(O(\sqrt{n})\)
当然,用\(map\)的话还要乘上一个\(log\)。
其实分块的时候\(j\)取到\(m\)可能会导致有些\(x\)被考虑到两次,但并不影响,而且边界还不怎么需要处理。
贴一下Luogu P3846(板子题)的代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int fpow(int a, int b, int c){
int ret = 1;
for (a %= c; b; b>>=1, a = 1ll*a*a % c) if (b&1) ret = 1ll * ret * a % c;
return ret;
}
int BSGS(int a, int b, int n, int &ret) {
int m = ceil(sqrt(n));
map<int,int> h;
for (int i = 0, tmp = b%n; i <= m; i++, tmp = 1ll*tmp*a%n)
h[tmp] = i;
a = fpow(a, m, n);
for (int tmp = a, i = 1; i <= m; i++, tmp = 1ll*tmp*a%n)
if (h.count(tmp)) { ret = 1ll*i*m - h[tmp]; return 1; }
return 0;
}
int main(){
int a, b, n, flg, ans; scanf("%d%d%d", &n, &a, &b);
flg = BSGS(a, b, n, ans);
if (!flg) puts("no solution"); else printf("%d\n", ans);
return 0;
}
还有比较毒瘤的就是如果\(a \equiv 0 (mod \ n)\)的时候,需要特判\(b \not\equiv 0 (mod \ n)\)
因为如果\(a\)是\(n\)的倍数,那怎么乘都是\(0\)...
所以板子在这里:
int BSGS(int a, int b, int n, int &ret) {
a %= n, b %= n;
if (a == 0) { if (b == 0) { ret = 0; return 1; } else return 0; }
int m = ceil(sqrt(n)); map<int,int> h;
for (int tmp = b%n, i = 0; i <= m; i++, tmp = 1ll*tmp*a % n) h[tmp] = i;
a = fpow(a, m, n);
for (int tmp = a%n, i = 1; i <= m; i++, tmp = 1ll*tmp*a % n)
if (h.count(tmp)) { ret = 1ll*i*m - h[tmp]; return 1; }
return 0;
}
\(ExBSGS\)的话。。。改天学吧
原文地址:https://www.cnblogs.com/wxq1229/p/12207157.html