当前位置:首页 > 技术 > 正文内容

BSGS 算法求解离散对数及其矩阵扩展

访客 技术 2026年10月26日 1

离散对数问题旨在求解满足同余方程 $a^x \equiv b \pmod p$ 的最小非负整数 $x$。当 $a$ 与 $p$ 互质时,大步小步算法(Baby-step Giant-step,简称 BSGS)能够在 $\Theta(\sqrt{p})$ 的时间复杂度内解决该问题。

根据欧拉定理,若 $\gcd(a, p) = 1$,则 $a^{\varphi(p)} \equiv 1 \pmod p$。这意味着指数 $x$ 具有周期性,解必然存在于 $0$ 到 $\varphi(p)$ 的范围内。为了优化暴力枚举,我们将 $x$ 表示为 $i \times m - j$ 的形式,其中 $m = \lceil \sqrt{p} \rceil$,$i \in [1, m]$,$j \in [0, m-1]$。

代入原方程可得:

$$a^{i \times m - j} \equiv b \pmod p$$

移项处理后得到:

$$a^{i \times m} \equiv b \times a^j \pmod p$$

采用减法形式 $i \times m - j$ 的核心优势在于避免了模逆元的计算。算法流程分为两个阶段:

  1. 小步(Baby Step): 枚举 $j$,计算 $b \times a^j \pmod p$ 并将结果存入哈希表,记录对应的 $j$ 值。
  2. 大步(Giant Step): 枚举 $i$,计算 $a^{i \times m} \pmod p$,并在哈希表中查找是否存在匹配值。若找到,则 $x = i \times m - j$ 即为解。

需要注意的是,若 $a \equiv 0 \pmod p$ 且 $b \not\equiv 0 \pmod p$,方程无解。

整数域实现

以下是基于上述原理的 C++ 实现。代码使用了快速幂算法来计算模幂,并利用 map 存储中间结果。

#include <iostream>
#include <map>
#include <cmath>
#include <algorithm>

using namespace std;
typedef long long ll;

const ll NOT_FOUND = -1;
ll mod_val, base_val, target_val;
ll block_size;
map<ll, ll> table;

ll read_int() {
    ll x = 0, sign = 1;
    char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') sign = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return x * sign;
}

ll power_mod(ll a, ll b, ll m) {
    ll res = 1;
    a %= m;
    while (b > 0) {
        if (b & 1) res = res * a % m;
        a = a * a % m;
        b >>= 1;
    }
    return res;
}

void build_table() {
    ll cur = target_val;
    for (ll j = 0; j < block_size; ++j) {
        if (table.find(cur) == table.end()) {
            table[cur] = j;
        }
        cur = cur * base_val % mod_val;
    }
}

ll search_solution() {
    ll step_val = power_mod(base_val, block_size, mod_val);
    ll cur = step_val;
    for (ll i = 1; i <= block_size; ++i) {
        if (table.find(cur) != table.end()) {
            return i * block_size - table[cur];
        }
        cur = cur * step_val % mod_val;
    }
    return NOT_FOUND;
}

int main() {
    mod_val = read_int();
    base_val = read_int();
    target_val = read_int();
    
    block_size = ceil(sqrt(mod_val));
    table.clear();
    
    build_table();
    ll ans = search_solution();
    
    if (ans == NOT_FOUND) {
        printf("no solution\n");
    } else {
        printf("%lld\n", ans);
    }
    return 0;
}

矩阵形式的扩展

BSGS 算法同样适用于矩阵离散对数问题,即求解 $A^x \equiv B \pmod p$,其中 $A, B$ 为方阵。由于矩阵乘法满足结合律且上述算法推导过程中未涉及除法运算,因此可以直接套用该框架。为了在哈希表中存储矩阵,需要设计合适的哈希函数将矩阵映射为整数值。

#include <iostream>
#include <map>
#include <cmath>
#include <cstring>

using namespace std;
typedef long long ll;

const ll INF = 1e18;
const unsigned int SEED1 = 13331;
const unsigned int SEED2 = 131;

int n;
ll mod_val;
ll block_size;
ll ans = INF;

struct Matrix {
    ll data[105][105];
    unsigned int h1, h2;

    void clear() {
        memset(data, 0, sizeof(data));
        h1 = h2 = 0;
    }

    void update_hash() {
        h1 = 0; h2 = 0;
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= n; ++j) {
                h1 = h1 * SEED1 + (unsigned int)data[i][j];
                h2 = h2 * SEED2 + (unsigned int)data[i][j];
            }
        }
    }

    Matrix operator*(const Matrix &other) const {
        Matrix res; res.clear();
        for (int i = 1; i <= n; ++i) {
            for (int k = 1; k <= n; ++k) {
                if (data[i][k] == 0) continue;
                for (int j = 1; j <= n; ++j) {
                    res.data[i][j] = (res.data[i][j] + data[i][k] * other.data[k][j]) % mod_val;
                }
            }
        }
        res.update_hash();
        return res;
    }
};

map<pair<unsigned int, unsigned int>, int> table;

ll read_int() {
    ll x = 0, sign = 1;
    char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') sign = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return x * sign;
}

Matrix power_mod(Matrix a, ll b) {
    Matrix res; res.clear();
    for (int i = 1; i <= n; ++i) res.data[i][i] = 1;
    res.update_hash();
    while (b > 0) {
        if (b & 1) res = res * a;
        a = a * a;
        b >>= 1;
    }
    return res;
}

void build_table(Matrix A, Matrix B) {
    table.clear();
    Matrix cur = B;
    for (int j = 0; j < block_size; ++j) {
        cur.update_hash();
        pair<unsigned int, unsigned int> key = {cur.h1, cur.h2};
        if (table.find(key) == table.end()) {
            table[key] = j;
        }
        cur = cur * A;
    }
}

void search_solution(Matrix A) {
    ans = INF;
    Matrix step = power_mod(A, block_size);
    Matrix cur = step;
    for (int i = 1; i <= block_size; ++i) {
        cur.update_hash();
        pair<unsigned int, unsigned int> key = {cur.h1, cur.h2};
        if (table.find(key) != table.end()) {
            ans = min(ans, (ll)i * block_size - table[key]);
        }
        cur = cur * step;
    }
}

int main() {
    n = read_int();
    mod_val = read_int();
    block_size = ceil(sqrt(mod_val));

    Matrix A, B;
    A.clear(); B.clear();

    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            A.data[i][j] = read_int();
    A.update_hash();

    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            B.data[i][j] = read_int();
    B.update_hash();

    build_table(A, B);
    search_solution(A);

    printf("%lld\n", ans);
    return 0;
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。