「浙江理工大学ACM入队200题系列」问题 F: 零基础学C/C++39——求方程的解
本题是浙江理工大学ACM入队200题第四套中的F题
我们先来看一下这题的题面.
由于是比较靠前的题目,这里插一句.各位新ACMer朋友们,请一定要养成仔细耐心看题的习惯,尤其是要利用好输入和输出样例.
- 样例相当于给你举了个具体的例子,可以帮助你更好的理解题目
- 样例会告诉你输入和输出的格式,你必须要在程序里以这样的格式输入和输出,否则会出问题
- 样例可以在你本地写完代码之后用作测试,来检查你的代码能否正常地运行(不过样例运行正确并不代表完全对了,可能输入其他的数据会出现别的问题)
题面
题目描述
求ax2+bx+c=0方程的实根。a,b,c由键盘输入. 解方程要考虑系数a等于零的情况,且解x1、x2必须是float型。a等于零有两种情况(b == 0,b!=0),a不等于零有三种情况(delta>0、==0、<0),先计算得到x1、x2,再printf输出
输入
输入三个数a,b,c
输出
输出方程的实根,如果方程有实根,则输出根;如果方程有2个不等实根,则分2行输出,第一行输出较大根,第二行输出较小根。 其余情况(如无实根等)则输出No
样例输入
1 -3 2
样例输出
2.000000
1.000000
代码风格问题
这题在ACM成员群里询问的频率的非常高,但其实并不是一道hard题,只是判断条件比较多容易乱而已,并不涉及什么高级的数据结构和算法.
在讲思路之前,首先来说说问这道题的朋友大多出现的两个坏习惯,不缩进和代码块不喜欢加大括号.
如果你想直接看与这题有关的解释,可以跳过这部分.
缩进问题
首先,一定要养成好好缩进的习惯.不缩进的代码在C中并不会报错,但是会极大降低自己代码的可读性.
可读性是很重要的,不管是实际开发还是ACM竞赛,都不是一个人的战场,你的代码是需要给你的同伴和队员看的,同时也是需要给未来的自己看的.你也不希望别人看不懂你的代码而导致时间成本的增加吧?
缩进的本质目的是为了展现出代码的层次,一份良好缩进的代码可以很清楚地展示出代码的层次结构,帮助阅读者更好地理解代码.
如果你很想养成好好缩进的习惯但是苦于经常忘记缩进或者不知道具体应该怎么缩进的话,强烈建议有空去学习一下Python语言,在Python中,不缩进会抛出异常,你必须要进行缩进.同时Python在ACM涉及到高精度的算法中,也有出场的一席之地.
大括号问题
其次就是不写大括号的问题了,这边我强烈建议所有新ACMer朋友们不要去管什么可以不写大括号的规则,一律打上大括号!在你不完全熟悉什么地方可以省略大括号的情况下随意省略大括号都可能会导致极其严重的问题!打一下大括号也不是什么特别麻烦的事情,所以请一律打上大括号!
我们来看看一些真实的不打大括号导致出问题的案例,均来自ACM成员群,进行过简化和改编.
错误样例1 不打大括号的嵌套if-else语句
首先,下面如下不打大括号的嵌套if-else语句:
int a = 10;
if(a > 5)
if(a > 10)
printf("a > 10");
else
printf("a < 5,I think");
从缩进上看,写出这样代码的人似乎想实现a > 10时输出"a > 10"而a <= 5时输出"a < 5,I think".但是,实际上根据else的匹配规则,此处的else是属于if(a > 10)的,所以此处的代码在运行之后会很滑稽地输出"a < 5,I think".但是打上大括号就可以直接解决并永远规避这个问题:
int a = 10;
if(a > 5)
{
if(a > 10)
{
printf("a > 10");
}
}
else
{
printf("a < 5,I think");
}
错误样例2 不打大括号的冒泡排序
接着,我们来看下面这一段不打大括号的冒泡排序代码(你不知道冒泡排序也没关系,只要看不打大括号的问题就好了,感兴趣可以去看我写的这篇博客,有详细的推出过程):
for (int i = 0; i < len; i++)
for(int j = len - 1;j > i;j--)
if(a[j - 1] < a[j])
int t = a[j];a[j] = a[j - 1];a[j - 1] = t;
根据冒泡排序的实现方式以及这位同学代码的写法,可以明白他是希望在a[j - 1] < a[j]满足时执行数据交换需要进行的这三句代码.尽管他很呆地把这三句代码写在了一行上,但依旧改变不了if语句在无大括号的时候只管一行语句的现实,要实现正常的功能,这里if语句应当加上大括号.
for (int i = 0; i < len; i++)
{
for(int j = len - 1;j > i;j--)
{
if(a[j - 1] < a[j])
{
int t = a[j];a[j] = a[j - 1];a[j - 1] = t;
}
}
}
限于篇幅原因这里就不再列举其他的自以为是不写大括号导致错误的错误样例了.总之,在没有完全熟悉if语句大括号省略规则的情况下,请完整地打出所有的大括号.另外即便你已经非常熟悉大括号省略的规则,也强烈建议不要省略任何大括号.仅仅打一对大括号并不是什么难事.
题目分析
我们完全可以模拟我们自己用纸笔解形二次方程的过程,慢慢写出各个分支语句.
首先,相信经历过高中毒打的各位朋友们在解一个形二次方程,第一反应都是讨论二次项系数是否为零.当二次项系数为零时,这便是一个形一次方程,同样要再讨论一下一次项系数是否为零,由数学知识可以得出这部分的代码:
// 定义3个double型变量a,b,c,注意这里使用直接定义为double型来解决整数除以整数还是整数的问题,下同.这题其实存在一个潜在的问题,即double中存在负0,这里我们直接无视这种情况(实际上此题特判-0反而会导致答案错误).
if (a == 0)
{
// 当前是形一次方程
if (b == 0)
{
// 一次项系数也为0,无穷解或无解
printf("No\n");
}
else
{
// 一次项系数不为0,为一次方程,直接解就好了(不知道为啥这里有朋友没有负号,或者是写成b / c,这边建议口算不行还是打个草稿好)
printf("%lf\n", -c / b);
}
}
如果掉进了此处对两个整数使用除法并企图得到一个小数的结果的老坑,或者对此还不熟悉的朋友可以去看看我写的这篇博客
之后就是解二次方程的问题了,为了后续编写方便,我们先计算delta:
double delta = b * b - 4 * a * c;
当delta小于零时,方程无解,我们直接输出No:
if(delta < 0)
{
// 方程无解
printf("No\n");
}
接下来,欢迎来到无数朋友的掉坑点.

听取WA声一片
常见错误思路及解决方案
优先级问题
当delta等于0时,二次方程仅有一解,相信经历过初中和高中朋友们都能很熟练地写出求这个唯一解的公式吧,于是就有朋友给出了如下代码(局部且无视if-else结构):
if(delta == 0)
{
// 方程仅有一解
printf("%lf\n", -b / 2 * a);
}
这里掉进的便是运算符优先级的坑.在数学中,同级运算符是从左往右运算的,在C中大体也是这样(不考虑结合性).因而在C语言中和数学一样,我们把一个分式改为除法运算式时,需要对分子分母添加括号(如果只有一项那当然不用加),这样才能保证优先级不出问题.局部参考代码如下(无视if-else结构):
if(delta == 0)
{
// 方程仅有一解
printf("%lf\n", -b / (2 * a)); // 时刻关注运算符优先级
}
想当然未实现由大到小输出
接下来当delta大于0时,依据题目要求我们需要将两根从大到小输出,于是就有很多朋友们给出如下代码(写成这样来问的也是最多的):
if(delta > 0)
{
// 方程有两解
double x1 = (-b + sqrt(delta)) / (2 * a); // 记得导入<math.h>头文件鸭!
double x2 = (-b - sqrt(delta)) / (2 * a);
printf("%lf\n%lf\n", x1, x2);
}
这里问题出在哪了呢?出在了没有将两根从大到小输出.可是每次当我指出这个问题时,都会有朋友们反驳道:"我考虑了啊,我就是从大到小输出的啊!"之所以出现这种情况,是因为这些朋友们想当然地以为二次方程求根公式中+的那个大而-的那个小.
实际上很明显并不是这样的,比如我们使用-3 4 5输入时,得到的输出便是-0.786300 2.119633(见下图)

这明显不是从大到小输出,因为决定这两个数大小的不仅是+还是-根号delta,还有其他的因素.当然,其他的因素同样不仅限于a的正负,有些朋友很呆地通过判断a的正负来当做判断两根的大小,这也是想当然了.
那怎么办呢,很容易啊,这不就是两个数降序输出的问题吗?直接比较交换就可以了(当然也有其他处理方法),局部参考代码如下(无视if-else结构):
if (delta > 0)
{
// 方程有两解
double x1 = (-b + sqrt(delta)) / (2 * a); // 记得导入<math.h>头文件鸭!
double x2 = (-b - sqrt(delta)) / (2 * a);
// 如果不满足降序,交换x1和x2
if (x1 < x2)
{
double t = x1;
x1 = x2;
x2 = t;
}
printf("%lf\n%lf\n", x1, x2);
}
参考代码
下面给出了我自己做这道题时候的完整代码:
(仅作为参考,一定要自己写一下奥,作弊没意思,害人又害己)
#include <stdio.h>
#include <math.h>
int main()
{
double a, b, c; // 这里使用直接定义为double型来解决整数除以整数还是整数的问题.这题其实存在一个潜在的问题,即double中存在负0,这里我们直接无视这种情况(实际上此题特判-0反而会导致答案错误).
scanf("%lf%lf%lf", &a, &b, &c);
if (a == 0)
{
// 当前是形一次方程
if (b == 0)
{
// 一次项系数也为0,无穷解或无解
printf("No\n");
}
else
{
// 一次项系数不为0,为一次方程,直接解就好了(不知道为啥这里有朋友没有负号,或者是写成b / c,这边建议口算不行还是打个草稿好)
printf("%lf\n", -c / b);
}
}
else
{
double delta = b * b - 4 * a * c;
if (delta < 0)
{
// 方程无解
printf("No\n");
}
else if(delta == 0)
{
// 方程仅有一解
printf("%lf\n", -b / (2 * a)); // 时刻关注运算符优先级
}
else
{
// 方程有两解
double x1 = (-b + sqrt(delta)) / (2 * a); // 记得导入<math.h>头文件鸭!
double x2 = (-b - sqrt(delta)) / (2 * a);
// 如果不满足降序,交换x1和x2
if (x1 < x2)
{
double t = x1;
x1 = x2;
x2 = t;
}
printf("%lf\n%lf\n", x1, x2);
}
}
return 0;
}
"正是我们每天反复做的事情,最终造就了我们,优秀不是一种行为,而是一种习惯" ---亚里士多德
这篇题解就到这里了,各位朋友如果有问题欢迎到acm成员群中提问哦!
「浙江理工大学ACM入队200题系列」问题 F: 零基础学C/C++39——求方程的解的更多相关文章
- 「浙江理工大学ACM入队200题系列」问题 E: 零基础学C/C++78——求奇数的乘积
本题是浙江理工大学ACM入队200题第八套中的E题 我们先来看一下这题的题面. 题面 输入 输入数据包含多个测试实例,每个测试实例占一行,每行的第一个数为n,表示本组数据一共有n个,接着是n个整数,你 ...
- 「浙江理工大学ACM入队200题系列」问题 B: 零基础学C/C++12——求平均值
本题是浙江理工大学ACM入队200题第二套中的B题 我们先来看一下这题的题面. 由于是比较靠前的题目,这里插一句.各位新ACMer朋友们,请一定要养成仔细耐心看题的习惯,尤其是要利用好输入和输出样例. ...
- 「浙江理工大学ACM入队200题系列」问题 L: 零基础学C/C++85——完美数
本题是浙江理工大学ACM入队200题第八套中的L题 我们先来看一下这题的题面. 题面 题目描述 任何一个自然数的约数中都有1和它本身,我们把小于它本身的因数叫做这个自然数的真约数. 如6的所有真约数是 ...
- 「浙江理工大学ACM入队200题系列」问题 K: 零基础学C/C++84——奇偶ASCII值判断
本题是浙江理工大学ACM入队200题第八套中的K题 我们先来看一下这题的题面. 题面 题目描述 任意输入一个字符,判断其ASCII是否是奇数,若是,输出YES,否则,输出NO; 例如,字符A的ASCI ...
- 「浙江理工大学ACM入队200题系列」问题 J: 零基础学C/C++83——宁宁的奥数路
本题是浙江理工大学ACM入队200题第八套中的J题 我们先来看一下这题的题面. 题面 题目描述 宁宁参加奥数班,他遇到的第一个问题是这样的:口口口+口口口=口口口,宁宁需要将1~9 九个数分别填进对应 ...
- 「浙江理工大学ACM入队200题系列」问题 L: 零基础学C/C++52——计算数列和2/1,3/2,5/3,8/5......
本题是浙江理工大学ACM入队200题第五套中的L题 我们先来看一下这题的题面. 题面 题目描述 有一分数序列:2/1,3/2,5/3,8/5,13/8,21/13,-- 计算这个数列的前n项和.注意: ...
- 「浙江理工大学ACM入队200题系列」问题 A: 零基础学C/C++34—— 3个数比较大小(冒泡排序与选择排序算法)
本题是浙江理工大学ACM入队200题第四套中的A题,同时给出了冒泡排序和选择排序算法 我们先来看一下这题的题面. 由于是比较靠前的题目,这里插一句.各位新ACMer朋友们,请一定要养成仔细耐心看题的习 ...
- 「浙江理工大学ACM入队200题系列」问题 H: 零基础学C/C++18——三位数反转
本题是浙江理工大学ACM入队200题第二套中的H题 我们先来看一下这题的题面. 由于是比较靠前的题目,这里插一句.各位新ACMer朋友们,请一定要养成仔细耐心看题的习惯,尤其是要利用好输入和输出样例. ...
- [Python] 文科生零基础学编程系列二——数据类型、变量、常量的基础概念
上一篇:[Python] 文科生零基础学编程系列--对象.集合.属性.方法的基本定义 下一篇: (仍先以最简单的Excel的VBA为例,语法与Python不同,但概念和逻辑需要理解透彻) p.p1 { ...
随机推荐
- PerfView专题 (第十篇):洞察 C# 终结队列引发的内存泄漏
一:背景 C# 程序内存泄漏的诱发因素有很多,但从顶层原理上来说,就是该销毁的 用户根 对象没有被销毁,从而导致内存中意料之外的对象无限堆积,导致内存暴涨,最终崩溃,这其中的一个用户根就是 终结器队列 ...
- WinUI(WASDK)项目实践——优雅的开发上位机应用(新)
摘要 这就是一个记录自己进行WinUI项目实践的博客,项目开源地址如下,觉得有帮助的可以去看看,因为项目都开源了,所以保姆级的讲解肯定不如直接看代码来的实在了. 电子脑壳项目地址 为什么叫新 因为之前 ...
- luogu [ZJOI2007] 矩阵游戏
[ZJOI2007] 矩阵游戏 题目描述 小 Q 是一个非常聪明的孩子,除了国际象棋,他还很喜欢玩一个电脑益智游戏――矩阵游戏.矩阵游戏在一个 \(n \times n\) 黑白方阵进行(如同国际象棋 ...
- Haproxy部署及控制台使用手册
一.介绍 1.简介 HAProxy是一个使用C语言编写开源软件,提供高可用,负载均衡,以及基于TCP(四层)和HTTP(七层)的应用程序代理: HAProxy特别适用于那些负载特大的web站点,这些站 ...
- 【读书笔记】C#高级编程 第十九章 程序集
(一)程序集的含义 程序集是.NET用于部署和配置单元的术语. .NET应用程序包含一个或多个程序集.通常扩展名是EXE或DLL的.NET可执行程序称为程序集. 程序集是自我描述的安装单元,由一个或多 ...
- 原生JavaScript对【DOM元素】的操作——增、删、改、查
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Windows服务器TLS协议
今天在Windows Admin Center里试图安装扩展插件的时候遇到一个问题.在可用插件里没有任何显示,包括各种微软自己开发的插件. 在Feeds里删除默认的链接,重新添加的时候也会遇到报错.说 ...
- UEC++ 多线程(一) FRunnable
虚幻官方文档:https://docs.unrealengine.com/5.0/en-US/API/Runtime/Core/HAL/FRunnable/ FRunnable "runna ...
- 升级openssl和openssh脚本
#!/bin/bash # 原先的版本号信息 # openssl version #OpenSSL 1.0.2k-fips 26 Jan 2017 # ssh -V #OpenSSH_7.4p1, O ...
- 配置Kubelet的垃圾回收
文章转载自:https://www.kuboard.cn/learning/k8s-advanced/gc.html Kubelet的垃圾回收功能可以清理不再使用的容器和镜像,kubelet对容器进行 ...