差别

GitHub跳转原题关系图返回列表

把原式改写成 |(a+bi)(p-qi)+(c+di)(r+si)|^2,在高斯整数环里用扩展欧几里得求 gcd 和贝祖系数。

OJ: luogu

题目 ID: P6299

难度:省选/NOI-

标签:数论数学最大公约数思维

日期: 2026-06-20 05:43

题意

给定 a,b,c,d,要求找到一组整数 p,q,r,s,使下面这个值的非零最小值尽量小:

M = | ... |

注意输出顺序是:

  • p q r s M

也就是最小值 M 在最后。

思路

先看一个最直接的小数据暴力:

cpp
#include <bits/stdc++.h>
using namespace std;

using i64 = long long;
using i128 = __int128_t;

const int LIM = 12;

struct Answer {
    i64 p, q, r, s;
    i128 m;
};

string to_string_i128(i128 x) {
    if (x == 0) {
        return "0";
    }

    bool neg = false;
    if (x < 0) {
        neg = true;
        x = -x;
    }

    string s;
    while (x > 0) {
        int digit = (int) (x % 10);
        s.push_back(char('0' + digit));
        x /= 10;
    }
    if (neg) {
        s.push_back('-');
    }
    reverse(s.begin(), s.end());
    return s;
}

i128 calc_value(i64 a, i64 b, i64 c, i64 d, i64 p, i64 q, i64 r, i64 s) {
    // M = |(a+bi)(p-qi) + (c+di)(r+si)|^2
    i128 real_part = (i128) a * p + (i128) b * q + (i128) c * r - (i128) d * s;
    i128 imag_part = (i128) b * p - (i128) a * q + (i128) c * s + (i128) d * r;
    return real_part * real_part + imag_part * imag_part;
}

bool better_answer(const Answer &a, const Answer &b) {
    if (a.m != b.m) return a.m < b.m;
    if (a.p != b.p) return a.p > b.p;
    if (a.q != b.q) return a.q > b.q;
    if (a.r != b.r) return a.r > b.r;
    return a.s > b.s;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    i64 a, b, c, d;
    cin >> a >> b >> c >> d;

    Answer best = {0, 0, 0, 0, -1};
    bool found = false;

    // 只用于小数据验证:直接枚举 p, q, r, s。
    for (i64 p = -LIM; p <= LIM; p++) {
        for (i64 q = -LIM; q <= LIM; q++) {
            for (i64 r = -LIM; r <= LIM; r++) {
                for (i64 s = -LIM; s <= LIM; s++) {
                    i128 now = calc_value(a, b, c, d, p, q, r, s);
                    if (now == 0) {
                        continue;
                    }

                    Answer cur = {p, q, r, s, now};
                    if (!found || better_answer(cur, best)) {
                        found = true;
                        best = cur;
                    }
                }
            }
        }
    }

    cout << best.p << ' '
         << best.q << ' '
         << best.r << ' '
         << best.s << ' '
         << to_string_i128(best.m) << '\n';

    return 0;
}

暴力版就是在一个很小的范围里枚举 p,q,r,s,直接代公式算出 M
这当然只能用来帮助理解和对拍,根本不可能应付正式数据。

这题真正的关键,是把那个长式子认成一个高斯整数范数

设:

  • A = a + bi
  • B = c + di
  • X = p - qi
  • Y = r + si

那么原式恰好就是:

M = |A X + B Y|^2

也就是说,我们实际上是在问:

  • A X + B Y 能取到的非零复整数里,范数最小的是谁

而高斯整数环是欧几里得整环,所以由 AB 生成的所有线性组合:

  • A X + B Y

构成一个主理想,它由高斯整数 gcd 生成。

于是最小非零范数,正好就是:

  • gcd(A, B) 的范数

这张图表达的就是这个关系:

flowchart LR
  A["所有线性组合 A·X+B·Y"] --> B["形成一个高斯整数理想"]
  B --> C["这个理想由 gcd(A,B) 生成"]
  C --> D["最小非零 M = |gcd(A,B)|^2"]

图里真正重要的是:
我们不再直接找 p,q,r,s,而是先去找高斯整数里的 gcd。
一旦求出了贝祖系数:

  • A X + B Y = g

就能直接从 X,Y 反推出 p,q,r,s

还有一个实现细节:
高斯整数 gcd 只差一个单位元 ±1, ±i 都是同一个答案。
为了让本地样例输出稳定,代码里会把这 4 组等价答案都试一遍,固定选字典序最大的那组输出。

代码

cpp
#include <bits/stdc++.h>
using namespace std;

using i64 = long long;
using i128 = __int128_t;

struct GaussInt {
    i128 x, y; // x + yi
};

struct Answer {
    i128 p, q, r, s;
};

GaussInt operator + (const GaussInt &a, const GaussInt &b) {
    return {a.x + b.x, a.y + b.y};
}

GaussInt operator - (const GaussInt &a, const GaussInt &b) {
    return {a.x - b.x, a.y - b.y};
}

GaussInt operator * (const GaussInt &a, const GaussInt &b) {
    return {
        a.x * b.x - a.y * b.y,
        a.x * b.y + a.y * b.x
    };
}

bool is_zero(const GaussInt &a) {
    return a.x == 0 && a.y == 0;
}

i128 norm(const GaussInt &a) {
    return a.x * a.x + a.y * a.y;
}

string to_string_i128(i128 x) {
    if (x == 0) {
        return "0";
    }

    bool neg = false;
    if (x < 0) {
        neg = true;
        x = -x;
    }

    string s;
    while (x > 0) {
        int digit = (int) (x % 10);
        s.push_back(char('0' + digit));
        x /= 10;
    }
    if (neg) {
        s.push_back('-');
    }
    reverse(s.begin(), s.end());
    return s;
}

// 把 a / b 四舍五入到最近的整数。
i128 round_div(i128 a, i128 b) {
    if (a >= 0) {
        return (a + b / 2) / b;
    }
    return -((-a + b / 2) / b);
}

// 高斯整数带余除法:a = bq + r,且 r 的范数足够小。
GaussInt gauss_div(const GaussInt &a, const GaussInt &b) {
    i128 den = norm(b);
    i128 real_up = a.x * b.x + a.y * b.y;
    i128 imag_up = a.y * b.x - a.x * b.y;
    return {
        round_div(real_up, den),
        round_div(imag_up, den)
    };
}

// 返回 gcd(a, b),并求出 ax + by = gcd(a, b) 的一组贝祖系数。
GaussInt exgcd(const GaussInt &a, const GaussInt &b, GaussInt &x, GaussInt &y) {
    if (is_zero(b)) {
        x = {1, 0};
        y = {0, 0};
        return a;
    }

    GaussInt q = gauss_div(a, b);
    GaussInt r = a - q * b;

    GaussInt x1, y1;
    GaussInt g = exgcd(b, r, x1, y1);

    x = y1;
    y = x1 - q * y1;
    return g;
}

GaussInt mul_unit(const GaussInt &a, int type) {
    if (type == 0) return a;             // 1
    if (type == 1) return {-a.x, -a.y}; // -1
    if (type == 2) return {-a.y, a.x};  // i
    return {a.y, -a.x};                 // -i
}

Answer decode_answer(const GaussInt &u, const GaussInt &v) {
    // 原式可以写成:
    // M = |(a+bi)(p-qi) + (c+di)(r+si)|^2
    return {u.x, -u.y, v.x, v.y};
}

bool better_answer(const Answer &a, const Answer &b) {
    if (a.p != b.p) return a.p > b.p;
    if (a.q != b.q) return a.q > b.q;
    if (a.r != b.r) return a.r > b.r;
    return a.s > b.s;
}

Answer normalize_answer(const GaussInt &u, const GaussInt &v) {
    bool first = true;
    Answer best = {0, 0, 0, 0};

    // 同乘一个单位元 ±1、±i,不会改变最小范数。
    // 为了让本地样例输出稳定,这里固定选字典序最大的那一组。
    for (int type = 0; type < 4; type++) {
        GaussInt nu = mul_unit(u, type);
        GaussInt nv = mul_unit(v, type);
        Answer cur = decode_answer(nu, nv);
        if (first || better_answer(cur, best)) {
            first = false;
            best = cur;
        }
    }

    return best;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    i64 a, b, c, d;
    cin >> a >> b >> c >> d;

    GaussInt A = {(i128) a, (i128) b};
    GaussInt B = {(i128) c, (i128) d};

    GaussInt u, v;
    GaussInt g = exgcd(A, B, u, v);

    Answer ans = normalize_answer(u, v);
    i128 M = norm(g);

    cout << to_string_i128(ans.p) << ' '
         << to_string_i128(ans.q) << ' '
         << to_string_i128(ans.r) << ' '
         << to_string_i128(ans.s) << ' '
         << to_string_i128(M) << '\n';

    return 0;
}

复杂度

高斯整数 exgcd 的复杂度和普通 exgcd 类似,取决于欧几里得算法的轮数。
在本题数据范围内可以看作对数级。

空间复杂度:

  • O(1)O(1)

总结

这题最难的地方不是实现,而是第一眼认公式。

一旦看出:

  • M = |(a+bi)(p-qi) + (c+di)(r+si)|^2

后面就变成一题高斯整数 gcd / 贝祖系数模板题了。

一图流解析

这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。

一图流解析