把较大根统一成 -b/(2a)+sqrt(Delta)/(2|a|),再用质因数分解或倒序枚举两种方式提净判别式里的平方因子并格式化输出。
OJ: luogu
题目 ID: P9750
难度:普及-
标签:数学模拟推导
创建: 2026-06-18 21:42
更新: 2026-10-07 12:15
形式化题目
给出
- 判别式
为负时方程无实数解,输出 NO; - 否则输出两个实数解中较大的那个,并且必须写成题面规定的两种形式之一:
- 最简有理数
,其中 、 , 时只写 ; - 根式
,其中 , 且不含平方因子(不存在 使 ), 时省略有理部分和它后面的加号。
- 最简有理数
这题真正容易错的不是复杂度,而是格式和符号:根号项系数必须为正、
解法总览
两种解法共享同一套化简骨架:
- 用判别式判断有没有实数解;
- 把较大根统一写成
,让根号项前面的系数天然为正,正好满足 ; - 把
拆成 ,其中 不再含平方因子; 、 各自约成最简分数,再按题面四种格式输出。
差别只在第 3 步怎么把
- 解法一(
main.cpp,正式主解):分解质因数,按指数的奇偶分拣平方因子; - 解法二(
main2.cpp):完全不分解,整数二分求出后从大到小枚举 ,第一个整除 的就是最大平方因子。
两个解法输入输出格式相同、结论一致,属于替代关系而不是层层优化。同目录的 brute.cpp 是解法二思路的朴素版本:从小到大枚举所有
解法一:分解质因数提取平方因子
思路
先算判别式
为负,直接输出 NO;是完全平方数,较大根一定是有理数,代入公式约分即可; - 否则较大根是无理数,走根式格式。
把较大根统一写成:
这样根号项前面的系数恒为正,天然满足题目要求的
接下来把
其中
其中:
最后分别把 sqrt(r),是整数写 c*sqrt(r),倒数是整数写 sqrt(r)/d,否则写 c*sqrt(r)/d。
代码
#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;
}复杂度
设判别式为
解法二:倒序枚举提取平方因子
思路
统一公式沿用解法一,区别只在第 3 步:不分解质因数,而是直接枚举
- 用整数二分求出
,不用 (long double) sqrt,避开浮点数在完全平方数边界上的舍入误差; - 从
开始往下枚举,第一个使 整除 的 就是最大平方因子,令 ;都不整除时 。
正确性很直接:题目要的
由于
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-02 08:45
* update_at: 2026-10-02 08:45
*/
// main2.cpp:第二种解法。整数二分求 sqrt(delta),再从大到小枚举平方因子提根号,
// 不分解质因数、不用浮点开方,最后按题面格式输出较大实根。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
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;
}
// 把分数约成最简形式,并保证分母为正。
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);
}
// 整数开方:返回最大的 s 满足 s * s <= x(x >= 0)。
// 二分求解,避免 (long double) sqrt 舍入带来的边界误差。
ll isqrt(ll x) {
ll left = 0;
ll right = 1;
while (right * right <= x) {
right *= 2;
}
ll answer = 0;
while (left <= right) {
ll mid = left + (right - left) / 2;
if (mid * mid <= x) {
answer = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return answer;
}
// 从大到小枚举 s,第一个满足 s * s 整除 delta 的 s 就是最大平方因子,
// 于是 delta = s * s * r,且 r 中不再含平方因子。
// delta = b*b - 4ac <= 10^6 + 4 * 10^6 = 5 * 10^6,所以 s 最多枚举到 2237 左右。
ll max_square_factor(ll delta) {
for (ll s = isqrt(delta); s >= 1; s--) {
if (delta % (s * s) == 0) {
return s;
}
}
return 1;
}
// 根号项的四种输出格式:1、整数、1/q、c/q(约分后 c、q 互质)。
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 = isqrt(delta);
// 判别式是完全平方数时,较大根一定是有理数,直接约分输出。
if (sq * sq == delta) {
ll num = -b + (a > 0 ? sq : -sq); // a < 0 时分母为负,取 -sq 让根号项仍为正
return fraction_to_string(make_fraction(num, 2 * a));
}
// 较大根统一写成 -b/(2a) + sqrt(delta)/(2|a|),这样根号项系数恒为正,
// 正好满足题目要求的 q2 > 0。
Fraction q1 = make_fraction(-b, 2 * a);
ll s = max_square_factor(delta); // delta = s * s * r
Fraction q2 = make_fraction(s, 2 * (a > 0 ? a : -a));
string res;
if (q1.num != 0) { // q1 = 0 时按题面跳过有理数部分,直接写根号项
res += fraction_to_string(q1);
res += "+";
}
res += radical_term_to_string(q2, delta / (s * s));
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;
}复杂度
单组数据的时间复杂度是
复杂度对比
| 解法 | 提平方因子的方式 | 单组时间 | 空间 | 特点 |
|---|---|---|---|---|
解法一 main.cpp |
分解质因数 | 正式主解,按指数奇偶分拣,思路通用 | ||
解法二 main2.cpp |
倒序枚举 | 纯整数、无分解,命中大平方因子时更早结束 |
两者同阶,任选其一都能通过;差别主要在写法长短和是否依赖浮点开方。
总结
这题本质是数学化简题:
- 用判别式判断有没有实数解;
- 用统一公式
锁定较大根; - 把分数约分;
- 把根号里的平方因子提干净;
- 按题目规定格式输出。
第 4 步可以用质因数分解做,也可以直接倒序枚举做,两种写法都只要
图示解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
