原根

考虑对于 \(i \in 0 \sim p - 1\)\(a^i \bmod p\) 的取值,其中 \(a \in [1, p), p\) 是素数。

首先 \(a^0 = a^{p - 1} \equiv 1(\bmod p)\),那么会出现一个循环结构。对于循环节中的任意两个数,可以证明这两个数不同,理由是如果 \(a^x = a^y\) 那么 \(a^{|x-y|} = 1\)。但是 \(x \neq y\),那么就矛盾了。

那么想象这样的事情的意义是什么。首先如果有循环节,那么这个循环节一定是 \(p - 1\) 的约数。其次在每一个循环节内,每一个数只会出现一次或零次(零次的话那么不存在 \(a^x \equiv i\) 的解)

如果这个循环节就是整个 \([0, p - 1)\) 也就是说 \(\forall i \in (0, p - 1), a^i \not \equiv 1\) 的话,那么称 \(a\) 为模 \(p\) 意义下的原根。

王元于 1959 年证明了若 \(m\) 有原根,其最小原根是不多于 \(m^{0.25}\) 级别的。此处略去证明。
因此找到一个数的原根只需要暴力从小到大去枚举就好了。时间复杂度 \(O(m^{0.25 \times \sqrt n} = m^{\frac{3}{4}})\)

在专业术语中,将模 \(m\)\(a^i\) 的循环节称作“阶”,记为 \(\delta_m(a)\)。那么有,如果 \(a^i \equiv 1(\bmod m)\) 那么 \(\delta_m(a) | i\)。如果有 \(a^p \equiv a^q(\bmod m)\) 那么 \(p \equiv q(\bmod \delta_m^a)\)

离散对数

\(a^x = p\),那么有 \(\log_a p = x\)

在模意义下定义的对数叫做离散对数,也就是说对数的结果是整数的意思。对于质数 \(p\) 的原根 \(r\),任何 \(a\) 均存在 \(\log_r a = t\) 使得 \(r^t \equiv a(\bmod p)\)。并且个数等于 \(1 + [a==1]\) 个(取模意义下)。

考虑应用。

对于 bsgs 算法,我们有一个预处理 \(a^{i\sqrt B}(i \sim O(\frac{p}{B}))\) 的过程,和一个查询 \(a^{i}(i \sim O(B))\) 的过程。注意这个 \(B\) 是可以调的。
那么对于 \(a\) 不固定但是 \(p\) 固定的话呢?我们可以利用原根 \(r\),先预处理 \(r^{i \sqrt B}\),然后对于每一个询问 \(a^{\$} = x\)\(\$\) 的最小值,可以做如下操作:

  • 求出离散对数 \(\log_r a = t\)
  • 求出 \(r^{?} \equiv x\)。这里问题变成了,\(r^t = a, r^{?} = x, a^{\$} = x\),那么 \(\$\) 应该等于 \(\cfrac{?}{t}\),但是可能不整除。于是用到下一个步骤。
  • 利用 \(r^{\varphi(p)} \equiv 1\),这里 \(\varphi(p) = p - 1\)(没有比它更小的了,这里 \(\varphi(p)\) 的作用和中国剩余定理里面 \(\bmod M\) 的关系很像,是最小循环节)。找到一个 $t | ? + x \varphi(p) $。
  • 那么一组解就是 \(\cfrac{? + x \varphi(p)}{t}\)(所有解都长这样)

注意这里的不变量是 \(x\)。那么令 \(?+ x\varphi(p)\) 等于令 \(x\) 最小。

对于前两项,就是简单的 BSGS 查询操作。

做 exgcd,但是要取模。

等等。exgcd 求出的解其实是有性质的!
性质是什么呢?

对于 \(ax +by = \gcd(x, y)\),其解 \(a_0, b_0\) 满足 \(|a_0| \le y, |b_0| \le x\)。因此只需要判断是否小于 \(0\) 并做最多一次加法即可。

exgcd 的写法这样,可以令 \(x_0\) 求出的是最小并且非负的:

const int inf = 1e9;
struct jie {
    int x, y, tx, ty;
    jie() {x = -inf, y = -inf, tx = 0, ty = 0;}  //这是无解返回的情况。
    jie(int x, int y, int tx, int ty): x(x), y(y), tx(tx), ty(ty) {}
};
void exgcd(int a, int b, int &x, int &y) {
    if(!a){x=0,y=1; return;}
    else {exgcd(b%a, a, y, x); x -= (b/a) * y;}
}
jie eqfc(int a, int b, int x, int y, int k) { //ax + by = k, ab 是常数,a<b
    int g = __gcd(a, b);
    if(k % g != 0) {return jie();}
    else {
        int xz, yz; exgcd(a, b, xz, yz); if(xz < 0) xz += b / g, yz -= a / g; //这里是为了让 xz 非负并且最小
        return jie(xz, yz, b / g, a / g);
    } 
}

那么这样让我们不需要每一次都预处理,时间复杂度降为 \(O(\cfrac{p}{B} + T \times (B + \log v))\)。注意瓶颈一般是 \(TB\),因为 exgcd 只是对求出来的答案进行一些处理罢了。

posted @ 2023-02-11 17:16  OIer某罗  阅读(19)  评论(0编辑  收藏  举报