loading...

Baby Step, Gaint Step

巨人的一大步是宝宝的一小步开始的。

BSGS 北上广深-介绍

求这样一个方程 \(a^x \equiv b \pmod p\),其中 \(p\) 是质数。

暴力做法是枚举 \(x\)

考虑这样一个做法,取一个数 \(m\),先暴力求出前 \(m\)\(a^1,a^2,\dots,a^m\)。然后令 \(x = mi-j(i\ge 1, 1 \le j \le m)\)。可以做出如下变换:

\[\begin{aligned} &a^x\equiv b\pmod p \\ \Leftrightarrow &a^{mi}\equiv a^jb \pmod p\\ \Leftrightarrow &a^{mi} \times b^{-1}\equiv a_j \pmod p \end{aligned} \]

这里,枚举 \(i\),时间复杂度为 \(\mathcal O\left(\dfrac{p}{m}\right)\),左边均可以直接计算。

右边直接预处理前 \(m\) 个幂,用 map 判断是否存在解。显然若存在多个 \(k\in [1,m]\) 满足 \(a^k\equiv q \pmod p\),应当记录最大的 \(k\)

时间复杂度 \(\mathcal O\left(m + \dfrac{p}{m}\log p\right)\),显然 \(m\)\(\sqrt{p\log p}\) 时复杂度最优,为 \(\mathcal O(\sqrt {p \log p})\),这个复杂度只停留在理论。其实直接取 \(\sqrt p\) 也可行。

例题 1 计算器

给出 \(y,z,p\)

  1. \(1\) 类询问:\(y^z \bmod p\)
  2. \(2\) 类询问:\(xy\equiv z \pmod p\) 的最小非负整数解。
  3. \(3\) 类询问:\(y^x\equiv z \pmod p\) 的最小非负整数解。

只考虑 3 类询问,是个模板。

注意:\(y,z\) 同为 \(0\) 时,答案为 \(1\)\(y,z\) 只有一个为 \(0\) 时,无解。

#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define per(i, r, l) for (int i = r; i >= l; --i)
#define LL long long
#define ULL unsigned long long
#define PII pair<int,int>
#define PLL pair<LL,LL>
#define FASTIO ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define ojs666 false
const int N = 1e5+5,mod = 998244353;
const LL inf = 1e18;
int typ;
LL y, z, p;
inline LL qpow(LL a, int b, int p) {
    LL res = 1;
    for (; b; b >>= 1, a = a*a%p) if (b & 1) res = res*a%p;
    return res;
}
inline void solve1() {
    cin >> y >> z >> p;
    cout << qpow(y, z, p) << '\n';
}
inline void solve2() {
    cin >> y >> z >> p;LL inv = qpow(y, p-2, p);
    if (inv == 0) cout << "Orz, I cannot find x!\n";
    else cout << z*inv%p << '\n';
}
inline void bsgs(int y, int z, int p) { // x^y=z(mod p), calculate y
    map<int,int> mp;
    int sq = __builtin_sqrt(p);
    LL tmp = 1;
    for (int i = 1; i <= sq; ++i) mp[tmp=tmp*y%p] = i;
    LL mul = qpow(y, sq, p), inv = qpow(z, p-2, p);
    LL now = mul;
    for (int j = sq; j-sq <= p; j += sq, now = now*mul%p) {
        if (mp.find(now*inv%p) != mp.end()) return cout << j-mp[now*inv%p] << '\n', void();
    }
    cout << "Orz, I cannot find x!\n";
}
inline void solve3() {
    cin >> y >> z >> p; y %= p, z %= p;
    if (z == 0 && y == 0) cout << "1\n";
    else if (z == 0 && y != 0 || z != 0 && y == 0) cout << "Orz, I cannot find x!\n";
    else bsgs(y, z, p);
}
signed main() {
	FASTIO;
    int _;
    cin >> _ >> typ;
         if (typ == 1) { while (_--) solve1(); }
    else if (typ == 2) { while (_--) solve2(); }
    else if (typ == 3) { while (_--) solve3(); }
	return 0;
}

例题 2 Lunar New Year and a Recursive Sequence

有一串 \(n\) 个数的数列,给你 \(b_1\sim b_k\)。当 \(i>k\) 时:

\[f_i=(\prod\limits_{j=1}^kf_{i-j}^{b_j})\bmod{998244353} \]

已知 \(f_1=f_2=\cdots=f_{k-1}=1,f_n=m\),问 \(f_k\) 可能是多少。

\(k \leq 100\)\(n \leq 10^9\)

极为套路。考虑令 \(f_k=x\),令 \(f_i=x^{c_i}\)

显然有 \(c_1=c_2=\cdots=c_{k-1}=0,c_k=1\)

\[c_i = \sum _{j=1}^kc_{i-j} \]

这个就很好做了,直接矩阵快速幂得到 \(c_n\),我们得到一个 \(c_n\) 次剩余方程:

\[x^{c_n}\equiv m \pmod{p} \]

这个直接假设 \(x=g^a \bmod p\)\(g\)\(p\) 的一个原根。因为 \(p=998244353\),可以钦定 \(g=3\)

方程化为

\[g^{ac_n}\equiv m \pmod p\\ (g^{c_n})^a\equiv m \pmod p \]

这是一个关于 \(a\) 的高次同余方程,除了 \(a\) 所有值都已确定,直接 BSGS 做就行了。

submission

posted @ 2025-04-12 12:35  goldspade  阅读(13)  评论(0)    收藏  举报