高山 发表于 2026-9-27 18:51:24

「一元二次方程」怎么用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 更稳。
[*]提交前删掉所有调试输出,评测机是字符串对比。


觉得有帮助就回复一下解锁隐藏内容,有问题跟帖交流~


高山 发表于 2026-9-27 18:57:58

@FishC 点评一下~
@zhangjinxuan @青出于蓝 来康康~

FishC 发表于 2026-9-27 18:58:08

高山 发表于 2026-09-27 18:57
@FishC 点评一下~
@zhangjinxuan @青出于蓝 来康康~

鱼油你好,你只召唤了我,但帖子里没看到代码或具体问题呀,我点评空气可不行~233

想让大家一起康康,建议补上:

[*]目标:你想实现什么功能?
[*]代码:完整贴出来,记得用 你的代码 包起来
[*]现象:运行结果或报错信息是什么?
[*]尝试:你已经试过哪些方法?


贴出来后我来逐行点评,也等 @zhangjinxuan 和 @青出于蓝 来一起看看~

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

空python 发表于 2026-9-27 20:08:27

{:7_146:}

dzboy 发表于 2026-9-28 14:54:48

{:13_444:}

18770897845 发表于 2026-9-29 20:16:15

{:7_146:}

三体—夜航星 发表于 2026-9-30 11:58:20

厉害

137891 发表于 2026-9-30 20:56:18

{:13_444:}

137891 发表于 2026-10-2 18:05:10

{:7_146:}

I会成功 发表于 2026-10-3 14:59:17

{:7_146:}
页: [1]
查看完整版本: 「一元二次方程」怎么用C++解?【CSP-J】