「一元二次方程」怎么用C++解?【CSP-J】
本帖最后由 高山 于 2026-9-27 18:55 编辑CSP-J 2023 T3 一元二次方程
P9750 · 洛谷 · 格式模拟题的格式化噩梦
图一地址
图一 主流程图:Δ 的三种情况走向
*本文部分由AI生成,经过了人工优化
如您对本教程有更好的想法,欢迎回复本帖,我们会给予鱼币、荣誉、贡献的额外奖励。
前言:这题为什么难
>>>可使用在线OJ评测本题
当你看惯了前面CSP出题人抽象的烤鸡问题
不如回归你的初中
看看一元二次方程 曾经的你
是如何度过的
表面是初中数学,实则是格式化输出 + 整数运算精度的陷阱合集。多数同学卡在:
[*]整数除法砍掉小数——用 double 一除,答案直接错。
[*]int 乘法溢出——4*a*c 先溢出再转 long long,救不回来。
[*]无理数格式规则多——整部、正负号、分母、r 不含平方因子,每一处都能 WA。
[*]读入顺序看反——第一行是 T M,T 在前 M 在后。
题目说了什么
第一行两个正整数 T(方程数量)和 M(系数绝对值上限)。接着 T 行,每行 a、b、c。
对每组输出一行:有实根输出较大的那个根;无实根输出 NO。输出字符串中间不能有任何空格。
输出格式(这题的核心)
场景要求例子
无实根输出 NONO
有理数q>0,gcd(p,q)=1;q=1 只输出 p1 或 -1/2
无理数写成 q1 + q2√r,r 无平方因子1+sqrt(2)/2
样例对照表(输入第 i 组 → 输出第 i 行)
#a b cΔ输出
11 -1 011
2-1 -1 -1-3NO
31 -2 101
41 5 49-1
54 4 10-1/2
61 0 -432172812*sqrt(3)
71 -3 153/2+sqrt(5)/2
82 -4 181+sqrt(2)/2
91 7 145-7/2+3*sqrt(5)/2
第一步:把 a 转正
方程两边同乘 -1,根不变,但 a 变成正数。好处:取较大的根时永远取加号那根,不用分情况。
if (a < 0) { a = -a; b = -b; c = -c; }
注意:这一步要在算 Δ 之前做!
void solve(int a, int b, int c) {
if (a < 0) { a = -a; b = -b; c = -c; } // ← 先转正
long long delta = (long long)b * b - 4LL * a * c;
// ...
}
第二步:算 Δ(别溢出!)long long delta = (long long)b * b - 4LL * a * c;
坑点:4*a*c 是 int 乘 int,会先溢出再转 long long,转也没用。
用 4LL 让整条乘法链都按 long long 算。
接着就是三分支 if-else:
if (delta < 0) {
cout << "NO\n";
return;
} else if (is_sq(delta)) {
// 有理数分支
} else {
// 无理数分支
}
★ 坑点:int 乘法溢出
错:
long long delta = (long long)b * b - (long long)4 * a * c;// a*c 已溢出
对:
long long delta = (long long)b * b - 4LL * a * c; // 整条链都是 long long
long long delta = (long long)b * b - 4LL * a * c; // 记住:4LL,不是 (long long)4
第三步:is_sq 判完全平方数
思路:开方取整,再平方回去验证。
图二地址
图二 is_sq 判断完全平方数流程
bool is_sq(long long n) {
if (n < 0) return false; // 负数不可能是平方
int sq = sqrt(n); // 赋给 int,自动截断(向下取整)
return (long long)sq * sq == n; // 平方回去验证
}
提醒:这函数只用来「判断」,别把 sq 当精确整数拿去做后续运算。
bool is_sq(long long n) {
if (n < 0) return false;
int sq = sqrt(n);
return (long long)sq * sq == n;
}
第四步:gcd 与约分输出
有理数、无理数都要约分,gcd 是全场复用的工具。
long long gcd(long long x, long long y) {
if (y == 0) return x;
return gcd(y, x % y);
}
约分三步走:
long long g = gcd(abs(num), abs(den));
num /= g; den /= g;
if (den < 0) { num = -num; den = -den; } // 负号归到分子,保证 den>0
**** Hidden Message *****
第五步:有理数分支
Δ 是完全平方数 → 根有理。取加号那根(a 已转正)。
long long sq = sqrt(delta);
long long num = -b + sq; // 分子
long long den = 2LL * a; // 分母
// 约分
long long g = gcd(abs(num), abs(den));
num /= g; den /= g;
if (den < 0) { num = -num; den = -den; }
// 输出
if (den == 1) cout << num << "\n";
else cout << num << "/" << den << "\n";
别用 double 除法! `(-b+sq)/2*a` 是整数除法,小数直接被砍掉。
第六步:抠平方因子 k 与 r
无理数要把 √Δ 拆成 k√r,r 不含平方因子。从 sq 往下试,找最大的 i 使 i² 整除 Δ。
图三地址
图三 抠平方因子求 k 与 r
long long sq = sqrt(delta);
long long k, r;
for (long long i = sq; i >= 1; i--) {
if (delta % (i * i) == 0) {
k = i;
r = delta / (i * i);
break;
}
}
例:Δ=1728=144×12 → k=12,r=12 → 12*sqrt(3)。
long long sq = sqrt(delta);
long long k, r;
for (long long i = sq; i >= 1; i--) {
if (delta % (i * i) == 0) { k = i; r = delta / (i * i); break; }
}
第七步:无理数格式化输出
x = q1 + q2√r,分三块:整部 q1、连接符、根号部 q2√r。
图四地址
图四 无理数格式化输出流程
[*]算 q1 = -b/(2a),约分,分母转正。
[*]算 q2 = k/(2a),约分,分母转正。
[*]整部不为 0:输出 q1,再输出 + 或 −(看 q2 符号)。
[*]整部为 0:不输出整部,但 q2 为负时要输出负号。
[*]根号部:q2=1 输出 sqrt(r);否则输出 {q2}*sqrt(r)(分母非 1 带上分母)。
验证:第 8 组 2 -4 1 → q1=1,q2=1/2,r=2 → 1+sqrt(2)/2。
// 整部
long long q1_num = -b, q1_den = 2LL * a;
long long g1 = gcd(abs(q1_num), abs(q1_den));
q1_num /= g1; q1_den /= g1;
if (q1_den < 0) { q1_num = -q1_num; q1_den = -q1_den; }
// 根号系数
long long q2_num = k, q2_den = 2LL * a;
long long g2 = gcd(abs(q2_num), abs(q2_den));
q2_num /= g2; q2_den /= g2;
if (q2_den < 0) { q2_num = -q2_num; q2_den = -q2_den; }
// 输出整部
if (q1_num != 0) {
print_rational(q1_num, q1_den);
if (q2_num > 0) cout << "+";
else { cout << "-"; q2_num = -q2_num; }
} else if (q2_num < 0) {
cout << "-";
q2_num = -q2_num;
}
// 输出根号部
if (q2_num == 1 && q2_den == 1) cout << "sqrt(" << r << ")\n";
else if (q2_den == 1) cout << q2_num << "*sqrt(" << r << ")\n";
else cout << q2_num << "*sqrt(" << r << ")/" << q2_den << "\n";
完整 AC 代码
**** Hidden Message *****
自查清单(提交前逐条核对)
[*]Δ 用 4LL,别用 (long long)4 —— 后者救不了 a*c 的溢出。
[*]别用 double 除法 —— 分子分母分开存、约分后输出。
[*]a 转正要在算 Δ 之前。
[*]约分后保证分母为正,负号归到分子。
[*]输出中间不能有空格,用 "\n" 不用 endl 更稳。
[*]提交前删掉所有调试输出,评测机是字符串对比。
觉得有帮助就回复一下解锁隐藏内容,有问题跟帖交流~
@FishC 点评一下~
@zhangjinxuan @青出于蓝 来康康~ 高山 发表于 2026-09-27 18:57
@FishC 点评一下~
@zhangjinxuan @青出于蓝 来康康~
鱼油你好,你只召唤了我,但帖子里没看到代码或具体问题呀,我点评空气可不行~233
想让大家一起康康,建议补上:
[*]目标:你想实现什么功能?
[*]代码:完整贴出来,记得用 你的代码 包起来
[*]现象:运行结果或报错信息是什么?
[*]尝试:你已经试过哪些方法?
贴出来后我来逐行点评,也等 @zhangjinxuan 和 @青出于蓝 来一起看看~
以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。 {:7_146:} {:13_444:} {:7_146:} 厉害 {:13_444:} {:7_146:} {:7_146:}
页:
[1]