「浙江理工大学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 { ...
 
随机推荐
- BeanUtils.copyProperties的使用方法
			
BeanUtils.copyProperties的使用方法 1.使用的是springframe包下的,BeanUtils.copyProperties(a,b) 把a属性拷贝给b属性 2.注意事项: ...
 - ARC120D Bracket Score 2 (模拟)
			
题面 给一个长度为 2 N 2N 2N 的序列 A A A,定义一个长度为 2 N 2N 2N 的合法括号序列的 得分(score) 为: 对于每对配对的括号 i , j i,j i,j, ∣ A i ...
 - 「题解报告」Blocks
			
P3503 Blocks 题解 原题传送门 思路 首先我们可以发现,若 \(a_l\) ~ \(a_r\) 的平均值大于等于 \(k\) ,则这个区间一定可以转化为都大于等于 \(k\) 的.我们就把 ...
 - Node.js躬行记(22)——Node环境升级日志
			
公司之前所有的 Node 项目,其环境都是 8.9.4 版本,发布于 2018 年的一个比较古老的版本. 老版本有两个比较明显的问题: Node 高版本的特性和方法都无法使用. 有些第三方新版本的包无 ...
 - flink-cdc同步mysql数据到hbase
			
本文首发于我的个人博客网站 等待下一个秋-Flink 什么是CDC? CDC是(Change Data Capture 变更数据获取)的简称.核心思想是,监测并捕获数据库的变动(包括数据 或 数据表的 ...
 - 在 Kubernetes 集群中使用 NodeLocal DNSCache
			
转载自:https://www.qikqiak.com/post/use-nodelocal-dns-cache/ NodeLocal DNSCache 通过在集群节点上运行一个 DaemonSet ...
 - 谈谈对K8S CNI、CRI和CSI插件的理解
 - 不要舔 Switch 游戏卡,单性生殖,永久夏令时
			
文章转载自:https://mp.weixin.qq.com/s/8EikwCvZgKt2TFsld-nKSA
 - 使用KVM安装windows10系统出现内存直接占满的情况解决
			
情况说明: 在使用kvm安装windows10系统的时候,采用的win10系统不是原版系统,而是经过进一步封装的系统,使用大白菜PE先格式化磁盘,然后再安装的系统,在系统安装好重启的时候,卡在安装界面 ...
 - rabbitmq的内存节点和磁盘节点
			
RabbitMQ集群里有内存节点与磁盘节点之分. 所谓内存节点,就是将元数据(metadata)都放在内存里,磁盘节点就是放在磁盘上.(内存节点将全部的队列,交换器,绑定关系,用户,权限,和vhost ...