按判别式分类讨论,先约分有理部分,再把判别式开方后提取最大平方因子并格式化输出较大实根。
OJ: luogu
题目 ID: P9750
难度:普及-
标签:数学模拟推导
日期: 2026-06-18 21:42
题意
给出 T 组一元二次方程
对每组方程:
- 如果没有实数解,输出
NO; - 如果有实数解,只输出两个实数解中较大的那个。
输出格式不能随便写小数,而要按题目要求写成:
- 最简整数或分数;
- 或者
这种根式形式,其中 q2 > 0,r里不能再含平方因子。
思路
先看一个可以直接验证想法的朴素解:
先算判别式 Delta < 0,直接无解。
如果 Delta 是完全平方数,较大根就是一个有理数,只要约分即可。
如果 Delta 不是完全平方数,就直接枚举 Delta 的最大平方因子,把 sqrt(Delta) 化成
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;
}这题真正容易错的不是复杂度,而是格式和符号。
为了统一“较大根”的写法,可以把答案写成:
这样根号项前面的系数天然为正,正好满足题目要求里的 q2 > 0。
接下来只要把 Delta 分解成:
其中 r 不再含平方因子,那么答案就能写成:
其中:
最后分别把 q1、q2 约成最简分数,再根据题目给出的四种格式输出即可。
代码
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,则单组数据的时间复杂度是
在本题的数据范围内,这个复杂度完全够用。
总结
这题本质是数学化简题:
- 用判别式判断有没有实数解;
- 用统一公式锁定较大根;
- 把分数约分;
- 把根号里的平方因子提出去;
- 按题目规定格式输出。
只要把这几步拆清楚,实现并不复杂。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
