离散对数
引入
对数的本质是构建一个乘法运算到加法运算的映射。仿照复数中构建对数运算的办法,可以在模
多值函数
如果对于自变量,有多于一个的函数值与其对应,这样的函数称为 多值函数。
多值函数的函数值为集合,值域为函数值集合的集合。多值函数常常首字母大写,并规定一个对应首字母小写的单值函数称为 主值。
在复数中构建的对数运算是多值函数:
它的主值是:
在模
离散对数
对于模
则称
因为
如果使用对数的记号书写,则构成多值函数关系:
它的主值是:
规定主值
与复对数一致,离散对数拥有性质:
与复对数一致,离散对数的主值不满足该性质。事实上,把离散对数的结果看作对
因此,为了概念的介绍方便,不再试图去硬性区分多值函数和多值函数的主值这两个概念,而是将记号直接记为:
这里后面的模数由
换底公式
如果模
使用原根为底的原因也得到了解释:为了使得模
大步小步算法
BSGS(baby-step giant-step),即大步小步算法。常用于求解离散对数问题。形式化地说,该算法可以在
其中
算法描述
令
我们已知的是 hash
/map
存下来,然后逐一计算
注意到 map
则多一个
注意到我们求出的是
进阶篇
求解
其中
该模型可以通过一系列的转化为成 基础篇 中的模型,你可能需要了解关于 阶与原根 的知识。
由于式子中的模数
方法一
我们令
于是就转换成了我们熟知的 BSGS 的基本模型了,可以在
方法二
我们仍令
方程两边同时取离散对数得到
我们可以通过 BSGS 求解
找到所有解
在知道
于是得到所有解为
对于上面这个式子,显然有
这就是原问题的所有解。
实现
下面的代码实现的找原根、离散对数解和原问题所有解的过程。
int gcd(int a, int b) { return a ? gcd(b % a, a) : b; }
int powmod(int a, int b, int p) {
int res = 1;
while (b > 0) {
if (b & 1) res = res * a % p;
a = a * a % p, b >>= 1;
}
return res;
}
// Finds the primitive root modulo p
int generator(int p) {
vector<int> fact;
int phi = p - 1, n = phi;
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) {
fact.push_back(i);
while (n % i == 0) n /= i;
}
}
if (n > 1) fact.push_back(n);
for (int res = 2; res <= p; ++res) {
bool ok = true;
for (int factor : fact) {
if (powmod(res, phi / factor, p) == 1) {
ok = false;
break;
}
}
if (ok) return res;
}
return -1;
}
// This program finds all numbers x such that x^k=a (mod n)
int main() {
int n, k, a;
scanf("%d %d %d", &n, &k, &a);
if (a == 0) return puts("1\n0"), 0;
int g = generator(n);
// Baby-step giant-step discrete logarithm algorithm
int sq = (int)sqrt(n + .0) + 1;
vector<pair<int, int>> dec(sq);
for (int i = 1; i <= sq; ++i)
dec[i - 1] = {powmod(g, i * sq * k % (n - 1), n), i};
sort(dec.begin(), dec.end());
int any_ans = -1;
for (int i = 0; i < sq; ++i) {
int my = powmod(g, i * k % (n - 1), n) * a % n;
auto it = lower_bound(dec.begin(), dec.end(), make_pair(my, 0));
if (it != dec.end() && it->first == my) {
any_ans = it->second * sq - i;
break;
}
}
if (any_ans == -1) return puts("0"), 0;
// Print all possible answers
int delta = (n - 1) / gcd(k, n - 1);
vector<int> ans;
for (int cur = any_ans % delta; cur < n - 1; cur += delta)
ans.push_back(powmod(g, cur, n));
sort(ans.begin(), ans.end());
printf("%d\n", ans.size());
for (int answer : ans) printf("%d ", answer);
}
扩展篇
接下来我们求解
其中
当
具体地,设
如果
同理,这样不停的判断下去。直到
记
由于
注意,不排除解小于等于
习题
-
SPOJ MOD 模板
-
SDOI2011 计算器 模板
-
LOJ6542 离散对数 index calculus 方法,非模板
本页面部分内容以及代码译自博文 Дискретное извлечение корня 与其英文翻译版 Discrete Root。其中俄文版版权协议为 Public Domain + Leave a Link;英文版版权协议为 CC-BY-SA 4.0。
贡献者:@东灯@WenzelTian@Great-designer@Zirnc
本页面最近更新:2/3/2023, 12:00:00 AM,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用