底とmodが互いに素でない離散対数問題
離散対数問題とは
整数 に対して,
を満たす最小の非負整数
を求める問題です.
例えば のとき,
より が解となります.
Baby-step Giant-stepアルゴリズム
底 と mod
が互いに素なときに使える離散対数問題の
解法です.
とします.
とおくと,
となります.
したがって を hash table に保存しておいて,
の順に存在判定をすれば良いです.
底とmodが互いに素でない場合
の素因数分解を
とおきます.
ただし
は
の素因数であり,
は
の素因数ではないとします.
このとき, に対して
は
の倍数になります.
したがって,
(1).
が
の倍数でないとき
の範囲で
の解は存在しません.
(2).
が
の倍数であるとき
に対して
が成立します.
と
は互いに素なので, これは Baby-step Giant-step アルゴリズムで解くことができます.
全体の流れ
については愚直に
かどうかを判定する. 解が見つかれば終了.
が
の倍数でないなら, 解なし.
を Baby-step Giant-step アルゴリズムで解く.
実装解説
離散対数問題(Mod-Log) | Luzhiled’s memo の実装を, 上記の説明に照らし合わせて解説します.
(※ 元のコードを一部変更しております.)
int64_t mod_log(int64_t a, int64_t b, int64_t p) { // g := p_1^{d_1} ... p_m^{d_m} を求める. int64_t g = 1; for(int64_t i = p; i; i /= 2) (g *= a) %= p; g = __gcd(g, p); // a^{x} が g の倍数になる手前までは愚直に計算する. int64_t t = 1, c = 0; for(; t % g; c++) { if(t == b) return c; (t *= a) %= p; } // a^{c} = t // b が g の倍数でないなら解なし. if(b % g) return -1; // a^{y} (t / g) = b / g mod p / g を解いて, y + c を解とする. t /= g; b /= g; p /= g; // a^{y} t = b mod p を解く. int64_t h = 0, gs = 1; // h := ceil(sqrt(p)) // gs := a^{h} for(; h * h < p; h++) (gs *= a) %= p; // a^{ih-j} t = b mod p (0 < i, j <= h) // (a^{h})^{i} t = b a^{j} mod p // (gs)^{i} t = b a^{j} mod p // b a^{j} (0 < j <= h) を bs に登録. unordered_map< int64_t, int64_t > bs; for(int64_t s = 0, e = b; s < h; bs[e] = ++s) { (e *= a) %= p; } // (gs)^{i} t (0 < i <= h) で bs を検索. y = s - bs[e] for(int64_t s = 0, e = t; s < p;) { (e *= gs) %= p; s += h; if(bs.count(e)) return c + s - bs[e]; } // 見つからなければ解なし. return -1; }