1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
//! 離散対数。
use super::carmichael_lambda;
use super::divisors;
use super::factors;
use super::gcd;
use super::mod_pow;
use std::collections::HashMap;
use carmichael_lambda::CarmichaelLambda;
use divisors::Divisors;
use factors::Factors;
use gcd::Gcd;
use mod_pow::ModPow;
/// 離散対数。
///
/// $n$ を法とする $\\mathrm{dlog}\_b(a)$。
/// $b^z \\equiv a \\pmod{n}$ なる $z\\ge 0$ が存在すれば、そのうち最小のものを返す。
/// $0\\le b, a\\lt n$ とする。
/// また、$0^0 = 1$ とする。
///
/// コーナーケースに注意。
/// $n=1$ なら $0^0=1\\equiv 0$ より $z=0$ を返す。
/// 以下、$n\\gt 1$ とする。
/// - $\\mathrm{dlog}_\\bullet(1) = 0$
/// - $\\mathrm{dlog}_0(0) = 1$
///
/// 上記に該当しないとき、$\\mathrm{dlog}_0(\\bullet)$ と $\\mathrm{dlog}_1(\\bullet)$
/// は存在しない。残りのケースについては、以下の方針に基づいて求める。
///
/// # Idea
/// $1\\to b\\to b^2\\to\\dots\\pmod{n}$ でできる functional graph を考えると、$\\rho$
/// の字状になっていることに気づく。しっぽの長さ $\\mu$ は高々 $\\log(n)$、頭の長さ
/// $\\lambda$ は $\\varphi(n)$ の約数になっていることが示せる。
///
/// そこで、まずしっぽの部分に解があるかを愚直に $O(\\mu)$ 時間で調べる。
/// 見つからなければ、頭の部分に解があるかを $o(\\lambda)$ 時間で調べる。
/// $O(\\sqrt{\\lambda}\\log(\\lambda))$ 時間のアルゴリズムを紹介する。
///
/// $\\rho$ の頭の部分は $b^\\mu\\to b^{\\mu+1}\\to\\dots\\to b^{\\mu+\\lambda}\\equiv b^\\mu$
/// である。このとき、${}^\\forall i\\ge\\mu$ について
/// $b^{i+1}\\cdot b^{\\lambda-1} \\equiv b^i \\pmod{n}$ が成り立つ。
/// すなわち、$b^{\\lambda-1}$ を掛けることで、$\\to$ をひとつ戻ることができる[^1]。
///
/// [^1]: $\\mu\\gt 0$ なら、 $b^\\mu$ は $b^{\\mu-1}$ と $b^{\\mu+\\lambda-1}$
/// ($\\neq b^{\\mu-1}$) のふたつから $\\to$ が入ってくるが、$\\rho$
/// の頭に含まれる後者に戻る。
///
/// また、頭の部分に $a$ が含まれるためには $\\gcd(b^\\mu, n)=\\gcd(a, n)$
/// が必要であり、これが成り立たなければ解なしを報告すればよい。
/// よって、以下ではこれが成り立つとする。
///
/// $z = \\mu+i\\cdot\\lfloor\\sqrt{\\lambda}\\rfloor+j$ ($0\\le j\\lt\\sqrt{\\lambda}$)
/// とし、$(i, j)$ を探すことを考える。
/// $$ \\begin{aligned}
/// b^{\\mu+i\\cdot\\lfloor\\sqrt{\\lambda}\\rfloor+j} &\\equiv a \\\\
/// b^{\\mu+i\\cdot\\lfloor\\sqrt{\\lambda}\\rfloor+j}\\cdot (b^{\\lambda-1})^j
/// &\\equiv a\\cdot (b^{\\lambda-1})^j \\\\
/// b^{\\mu+i\\cdot\\lfloor\\sqrt{\\lambda}\\rfloor}
/// &\\equiv a\\cdot (b^{\\lambda-1})^j \\\\
/// \\end{aligned} $$
/// これより、$i$ と $j$ を分離でき、$a\\cdot(b^{\\lambda-1})^j\\mapsto j$
/// の連想配列を作っておくことで、$b^{\\mu+i\\cdot\\lfloor\\sqrt{\\lambda}\\rfloor}$
/// に対応する要素があれば返せる。ただし、複数の $j$ について
/// $a\\cdot(b^{\\lambda-1})^j$ が同じ値を取りうるので注意する必要がある。
/// $j$ による小さい幅で連想配列を作り、$i$ による大きい幅でそれにアクセスする様子から
/// baby-step giant-step algorithm と呼ばれている。
///
/// ## About $\\mu$ and $\\lambda$
///
/// $\\mu$ と $\\lambda$ を求める方法について述べる。
/// 自然数 $x$ が素数 $p$ で割り切れる回数、すなわち $x$ の素因数分解における $p$
/// の指数を $\\pi\_p(x)$ で表すとする[^2]。
/// $b = \\prod\_{p\_i:\\text{ prime}} p\_i^{e\_i}$ とすると、次が成り立つ。
/// $$ \\mu = \\max\_i \\left\\lceil\\frac{\\pi\_{p\_i}(n)}{e\_i}\\right\\rceil. $$
/// また、$p = \\prod\_i p\_i^{\\pi\_{p\_i}(n)}$, $n\' = n / p$ とする。すなわち、$n$
/// を $b$ の各素因数で割り切れるだけ割ったものを $n\'$ とする。このとき、$\\lambda$
/// は $\\varphi(n\')$ の約数[^3]であり、次の式に基づき愚直に求める[^4]。
/// $$ \\lambda = \\min\_{i\\sqsubseteq \\varphi(n\')} i\\quad\\text{s.t.}\\quad
/// b^\\mu\\cdot b^i \\equiv b^\\mu \\pmod{n}. $$
/// $\\varphi(n\')$ の約数の個数は $o(\\sqrt{n}/\\log(n))$
/// なので、繰り返し二乗法を用いて $O(\\sqrt{n})$ 時間でできる。
/// 実際には、解が見つかった時点で打ち切れるので、$O(\\lambda + \\sum\_{i\\sqsubseteq
/// \\varphi(n\')}^{i\\le\\lambda} \\log(i))$ 時間になる。前半の $\\lambda$
/// は試し割り法に由来する項なので、約数列挙を $O(1)$ delay でできるなら消去できる。
///
/// [^2]: 標準的な記法があったら知りたいです。
///
/// [^3]: $\\varphi(n)$ と $n\'$ の定義から、$\\varphi(n\')$ は $\\varphi(n)$ の約数であることに注意。
///
/// [^4]: $x\\sqsubseteq y$ で $x$ が $y$ を割り切ることを表す。$x\\mid y$
/// は方向がわかりにくくて嫌い。素因数の多重集合の包含関係のつもり。$\\subseteq$ でもいいかも?
///
/// ## Proof
/// まず、$\\lambda$ が $\\varphi(n\')$ の約数になることを示す。
/// $b^\\mu$ における $p\_i$ の指数は $e\_i\\cdot\\max\_j \\lceil\\pi\_{p\_j}(n)/e\_j\\rceil$
/// であり、特に $e\_i\\cdot \\lceil\\pi\_{p\_i}(n)/e\_i\\rceil \\ge \\pi\_{p\_i}(n)$ 以上である。
/// よって、$b^\\mu$ は $p$ で割り切れ、$b^\\mu \\equiv 0\\pmod{p}$ である。これより、$b^{\\mu+i}
/// \\equiv 0\\pmod{p}$ ($i\\ge 0$) である。
///
/// また、$b$ と $n\'$ は互いに素なので、Euler の定理より $b^{\\varphi(n\')} \\equiv 1\\pmod{n\'}$
/// が成り立つ。よって、$b^{\\mu+\\varphi(n\')}\\equiv b^\\mu \\pmod{n\'}$
/// である。上記の $p$ に関する結果と合わせて $b^{\\mu+\\varphi(n\')}\\equiv b^\\mu \\pmod{n}$
/// を得る。よって、$\\lambda$ が $\\varphi(n\')$ の約数であることが示された。
///
/// 次に、$\\mu$ の値が正しいことを示す。$b^\\mu$ が頭の部分に含まれることは上で示した
/// $b^{\\mu+\\varphi(n\')}\\equiv b^\\mu\\pmod{n}$ より従うので、$\\mu\\gt 0$ のとき
/// $b^{\\mu-1}$ が頭の部分に含まれないことを示せば十分である。
/// $\\mu$ の定義より、ある $i$ に対して $e\_i\\cdot(\\mu-1)\\lt \\pi\_{p\_i}(n)$ が成り立つ。
/// よって $b^{\\mu-1}$ は $p$ は割り切れず、$b^{\\mu-1} \\not\\equiv 0\\pmod{n}$ である。
/// これにより、頭の部分に含まれるどの要素とも等しくないことがわかる。
///
/// 最後に、$\\gcd(b^\\mu, n)=\\gcd(a, n)$ であることが、頭の部分に $a$ が存在する、すなわち
/// ${}^\\exists i\\ge 0: b^{\\mu+i} \\equiv a\\pmod{n}$ であることの必要条件であることを示す。
///
/// - $\\gcd(b^{\\mu+i})\\equiv 0\\pmod{p}$ for $i\\ge 0$
/// - $\\gcd(b, n\') = 1$ より $\\gcd(b\^{\\mu+i}, n\') = 1$ for $i\\ge 0$
/// - $\\gcd(p, n\') = 1$
/// - $n=p\\cdot n\'$
///
/// より、$\\gcd(b^{\\mu+i}, n) = p$ ($i\\ge 0$) で一定となる。
/// すなわち、$\\gcd(a, n) = p$ が必要となる。
///
/// # Complexity
/// $O(\\sqrt{n} + \\sqrt{\\lambda}\\cdot H(\\sqrt{\\lambda}))$ time.
///
/// ただし、$H(n)$ は要素数 $n$ の [`HashMap`] の [`insert`] と [`get`] にかかる時間とする。
/// BS/GS パートでは [`BTreeMap`] を用いるよりも [`HashMap`]
/// を用いた方が高速だったので、とりあえずそうした。
///
/// [`BTreeMap`]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html
///
/// [`HashMap`]: https://doc.rust-lang.org/std/collections/struct.HashMap.html
/// [`insert`]: https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.insert
/// [`get`]: https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.get
///
/// ここで、$\\lambda$ は最悪 $\\Theta(n)$ である。
/// また、篩の前計算などで $n$, $b$ の素因数分解と $\\varphi(n)$ の約数列挙を $O(1)$ delay
/// でできるなら $\\sqrt{n}$ の項をそれらの個数に落とせる。
///
/// # Implementation notes
/// $\\lambda$ を求める際に $b^{\\mu+i}\\equiv b^\\mu$ なる最小の $i\\sqsubseteq \\varphi(n\')$
/// を探したが、実際には Euler の定理ではなく Carmichael の定理に基づいて
/// [$\\lambda(n\')$][`carmichael_lambda`]
/// の約数を探せばよい。関数値を求めるの自体は $\\varphi(\\bullet)$ よりも $\\lambda(\\bullet)$
/// の方が若干複雑だが、全体では高速に動作したので後者を選択した。
///
/// [`carmichael_lambda`]: fn.carmichael_lambda.html
///
/// # Examples
/// ```
/// use nekolib::math::DLog;
///
/// assert_eq!(6_u64.dlog(5, 13), Some(9));
/// assert_eq!(27_u64.dlog(3, 30), Some(3));
/// assert_eq!(2_u64.dlog(0, 4), Some(2));
/// assert_eq!(0_u64.dlog(1, 2), Some(0));
/// assert_eq!(2_u64.dlog(3, 10), None);
/// ```
///
/// # References
/// - <https://divinejk.hatenablog.com/entry/2021/02/07/133155>
///
/// # Suggestions
/// 内部で ~~Euler の $\\varphi$ 関数~~ Carmichael の $\\lambda$
/// 関数と約数列挙と素因数分解が必要となるので、
/// 篩などを用いて高速化が図れる場合に対応しやすいようにしてみるとよい?
///
/// ```ignore
/// pub fn dlog<I, J>(
/// b: u64, a: u64, n: u64,
/// // euler_phi: impl Fn(u64) -> u64,
/// carmichael_lambda: impl Fn(u64) -> u64,
/// divisors: impl Fn(u64) -> I,
/// factors: impl Fn(u64) -> J,
/// ) -> Option<u64>
/// where
/// I: Iterator<Item = u64>,
/// J: Iterator<Item = (u64, u32)>,
/// { ... }
/// ```
///
/// 一方で、そうでない場合にわざわざ関数を渡す必要があるので単に面倒かも。
/// お手軽パターンと二つ用意するといいかも?(デフォルト引数欲しいね)
///
/// とはいえ、結局 BS/GS パートが律速になってしまいそう。
///
/// # Naming
/// 関数名は *d*iscrete *log*arithm、引数 $b$ は *b*ase、$a$ は *a*ntilogarithm から。
pub trait DLog: Sized {
fn dlog(self, a: Self, n: Self) -> Option<Self>;
}
trait DLogInternal: Sized {
fn bs_gs(self, bb: Self, a: Self, n: Self, c: Self) -> Option<Self>;
}
macro_rules! impl_uint {
($t:ty) => {
impl DLog for $t {
fn dlog(self, a: Self, n: Self) -> Option<Self> {
let b = match (self, a, n) {
(_, _, 0) => panic!("modulo must be positive"),
(_, _, 1) => return Some(0),
(_, 1, _) => return Some(0),
(0, 0, _) => return Some(1),
(0, _, _) => return None,
(1, _, _) => return None,
_ => self,
};
let mut n_ = n;
let tail = b.factors()
.map(|(p, e)| {
let e = e as $t;
let mut f = 0;
while n_ % p == 0 {
n_ /= p;
f += 1;
}
(f + e - 1) / e
}).max().unwrap();
let mut bpow = 1;
for i in 0..tail {
if bpow == a {
return Some(i);
}
bpow = bpow * b % n;
}
let bb = bpow;
if a == 0 {
return (bb == 0).then(|| tail);
}
if n != n_ * a.gcd(n) {
return None;
}
let c = n_
.carmichael_lambda()
.divisors()
.find(|&c| (bb * b.mod_pow(c, n)) % n == bb)
.unwrap();
b.bs_gs(bb, a, n, c).map(|head| tail + head)
}
}
impl DLogInternal for $t {
fn bs_gs(self, bb: Self, a: Self, n: Self, c: Self) -> Option<Self> {
let b = self;
let step = (1..).find(|&i| i * i * 2 >= c).unwrap();
let seen = {
let mut seen = HashMap::new();
let baby_recip = b.mod_pow(c - 1, n);
let mut x = a;
for i in 0..step {
seen.insert(x, i);
x = (x * baby_recip) % n;
}
seen
};
let giant = b.mod_pow(step, n);
let mut x = bb;
for i in 0..=c / step {
if let Some(&e) = seen.get(&x) {
return Some(i * step + e);
}
x = (x * giant) % n;
}
None
}
}
};
( $($t:ty)* ) => { $(impl_uint!($t);)* };
}
impl_uint!(u8 u16 u32 u64 u128 usize);
#[test]
fn test() {
use std::collections::hash_map::Entry::{Occupied, Vacant};
let n_max = 200_u64;
for n in 1..=n_max {
for b in 0..n {
let expected = {
let mut expected = HashMap::new();
let mut x = 1 % n;
for i in 0.. {
match expected.entry(x) {
Vacant(v) => v.insert(i),
Occupied(_) => break,
};
x = x * b % n;
}
expected
};
for a in 0..n {
let z = b.dlog(a, n);
assert_eq!(z, expected.get(&a).cloned());
}
}
}
}