[CSP-J 2023] 一元二次方程

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

按判别式分类讨论,先约分有理部分,再把判别式开方后提取最大平方因子并格式化输出较大实根。

OJ: luogu

题目 ID: P9750

难度:普及-

标签:数学模拟推导

日期: 2026-06-18 21:42

题意

给出 T 组一元二次方程 ax2+bx+c=0ax^2 + bx + c = 0,其中 a!=0a != 0
对每组方程:

  • 如果没有实数解,输出 NO
  • 如果有实数解,只输出两个实数解中较大的那个。

输出格式不能随便写小数,而要按题目要求写成:

  • 最简整数或分数;
  • 或者 q1+q2sqrt(r)q1 + q2*sqrt(r) 这种根式形式,其中 q2 > 0r 里不能再含平方因子。

思路

先看一个可以直接验证想法的朴素解:

先算判别式 Delta=b24acDelta = b^2 - 4ac。如果 Delta < 0,直接无解。
如果 Delta 是完全平方数,较大根就是一个有理数,只要约分即可。
如果 Delta 不是完全平方数,就直接枚举 Delta 的最大平方因子,把 sqrt(Delta) 化成 dsqrt(r)d*sqrt(r)

cpp
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;

using ll = long long;

struct Fraction {
    ll num, den;
};

int T, M;
ll a, b, c;

ll gcd_ll(ll x, ll y) {
    if (x < 0) x = -x;
    if (y < 0) y = -y;
    while (y != 0) {
        ll t = x % y;
        x = y;
        y = t;
    }
    return x;
}

ll abs_ll(ll x) {
    return x >= 0 ? x : -x;
}

Fraction make_fraction(ll num, ll den) {
    if (den < 0) {
        num = -num;
        den = -den;
    }
    ll g = gcd_ll(num, den);
    num /= g;
    den /= g;
    return {num, den};
}

string fraction_to_string(Fraction x) {
    if (x.den == 1) {
        return to_string(x.num);
    }
    return to_string(x.num) + "/" + to_string(x.den);
}

// 暴力枚举最大的平方因子 best^2,使得 best^2 | delta。
void extract_square_part_bruteforce(ll delta, ll &square_part, ll &rest) {
    square_part = 1;
    for (ll d = 1; d * d <= delta; d++) {
        ll sq = d * d;
        if (delta % sq == 0) {
            square_part = d;
        }
    }
    rest = delta / (square_part * square_part);
}

string radical_term_to_string(Fraction coef, ll rest) {
    if (coef.den == 1) {
        if (coef.num == 1) {
            return "sqrt(" + to_string(rest) + ")";
        }
        return to_string(coef.num) + "*sqrt(" + to_string(rest) + ")";
    }

    if (coef.num == 1) {
        return "sqrt(" + to_string(rest) + ")/" + to_string(coef.den);
    }
    return to_string(coef.num) + "*sqrt(" + to_string(rest) + ")/" + to_string(coef.den);
}

string solve_one() {
    ll delta = b * b - 4 * a * c;
    if (delta < 0) {
        return "NO";
    }

    ll sq = (ll) sqrtl((long double) delta);
    while ((sq + 1) * (sq + 1) <= delta) sq++;
    while (sq * sq > delta) sq--;

    if (sq * sq == delta) {
        ll num = -b + (a > 0 ? sq : -sq);
        Fraction ans = make_fraction(num, 2 * a);
        return fraction_to_string(ans);
    }

    Fraction q1 = make_fraction(-b, 2 * a);

    ll square_part, rest;
    extract_square_part_bruteforce(delta, square_part, rest);
    Fraction q2 = make_fraction(square_part, 2 * abs_ll(a));

    string res;
    if (q1.num != 0) {
        res += fraction_to_string(q1);
        res += "+";
    }
    res += radical_term_to_string(q2, rest);
    return res;
}

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

    cin >> T >> M;
    while (T--) {
        cin >> a >> b >> c;
        cout << solve_one() << '\n';
    }

    return 0;
}

这题真正容易错的不是复杂度,而是格式和符号。

为了统一“较大根”的写法,可以把答案写成:

b/(2a)+sqrt(Delta)/(2a)-b / (2a) + sqrt(Delta) / (2|a|)

这样根号项前面的系数天然为正,正好满足题目要求里的 q2 > 0

接下来只要把 Delta 分解成:

Delta=s2rDelta = s^2 * r

其中 r 不再含平方因子,那么答案就能写成:

q1+q2sqrt(r)q1 + q2 * sqrt(r)

其中:

  • q1=b/(2a)q1 = -b / (2a)
  • q2=s/(2a)q2 = s / (2|a|)

最后分别把 q1q2 约成最简分数,再根据题目给出的四种格式输出即可。

代码

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

using ll = long long;

struct Fraction {
    ll num, den;
};

int T, M;
ll a, b, c;

ll gcd_ll(ll x, ll y) {
    if (x < 0) x = -x;
    if (y < 0) y = -y;
    while (y != 0) {
        ll t = x % y;
        x = y;
        y = t;
    }
    return x;
}

ll abs_ll(ll x) {
    return x >= 0 ? x : -x;
}

// 把分数化成最简形式,并保证分母为正。
Fraction make_fraction(ll num, ll den) {
    if (den < 0) {
        num = -num;
        den = -den;
    }
    ll g = gcd_ll(num, den);
    num /= g;
    den /= g;
    return {num, den};
}

string fraction_to_string(Fraction x) {
    if (x.den == 1) {
        return to_string(x.num);
    }
    return to_string(x.num) + "/" + to_string(x.den);
}

// 把 delta 分解成 square_part^2 * rest,其中 rest 为平方因子已经提干净后的数。
void extract_square_part(ll delta, ll &square_part, ll &rest) {
    square_part = 1;
    rest = 1;

    ll x = delta;
    for (ll p = 2; p * p <= x; p++) {
        if (x % p != 0) {
            continue;
        }
        int cnt = 0;
        while (x % p == 0) {
            x /= p;
            cnt++;
        }
        for (int i = 0; i < cnt / 2; i++) {
            square_part *= p;
        }
        if (cnt % 2 == 1) {
            rest *= p;
        }
    }
    if (x > 1) {
        rest *= x;
    }
}

string radical_term_to_string(Fraction coef, ll rest) {
    if (coef.den == 1) {
        if (coef.num == 1) {
            return "sqrt(" + to_string(rest) + ")";
        }
        return to_string(coef.num) + "*sqrt(" + to_string(rest) + ")";
    }

    if (coef.num == 1) {
        return "sqrt(" + to_string(rest) + ")/" + to_string(coef.den);
    }
    return to_string(coef.num) + "*sqrt(" + to_string(rest) + ")/" + to_string(coef.den);
}

string solve_one() {
    ll delta = b * b - 4 * a * c;
    if (delta < 0) {
        return "NO";
    }

    ll sq = (ll) sqrtl((long double) delta);
    while ((sq + 1) * (sq + 1) <= delta) sq++;
    while (sq * sq > delta) sq--;

    // 判别式是完全平方数时,答案一定是有理数。
    if (sq * sq == delta) {
        ll num = -b + (a > 0 ? sq : -sq);
        Fraction ans = make_fraction(num, 2 * a);
        return fraction_to_string(ans);
    }

    // 较大实根统一写成 -b/(2a) + sqrt(delta)/(2|a|),这样根号项系数恒为正。
    Fraction q1 = make_fraction(-b, 2 * a);

    ll square_part, rest;
    extract_square_part(delta, square_part, rest);
    Fraction q2 = make_fraction(square_part, 2 * abs_ll(a));

    string res;
    if (q1.num != 0) {
        res += fraction_to_string(q1);
        res += "+";
    }
    res += radical_term_to_string(q2, rest);
    return res;
}

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

    cin >> T >> M;
    while (T--) {
        cin >> a >> b >> c;
        cout << solve_one() << '\n';
    }

    return 0;
}

复杂度

设判别式为 Delta,则单组数据的时间复杂度是 O(sqrt(Delta))O(sqrt(Delta)),空间复杂度是 O(1)O(1)
在本题的数据范围内,这个复杂度完全够用。

总结

这题本质是数学化简题:

  1. 用判别式判断有没有实数解;
  2. 用统一公式锁定较大根;
  3. 把分数约分;
  4. 把根号里的平方因子提出去;
  5. 按题目规定格式输出。

只要把这几步拆清楚,实现并不复杂。

一图流解析

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

一图流解析