NOIP学习小站
西安交通大学附属中学航天学校

顺序结构例题

本小节通过顺序结构例题解析,更加深入探讨顺序结构。例题问题的解决通过编程解决问题的思路和步骤来组织,希望大家要注意并养成好的编程习惯,注重编码前问题的分析和算法的设计!

1.海伦公式计算三角形面积

问题背景:已知三角形的三边长 \(a、b、c\),则三角形的面积可使用海伦公式来计算:

令 \(p = (a+b+c)/2\),则三角形面积 \(S = \sqrt{p(p-a)(p-b)(p-c)}\)

问题描述:计算给定三边长的三角形的面积

输入格式:三角形的三边长,用空格隔开(数据保证能组成三角形)

输出格式:三角形的面积,保留2位小数

问题分析:输入三个浮点数存储到double变量中,利用问题背景提供的公式来计算

算法设计

  1. \(Input \ a,b,c\)
  2. \(p ← (a+b+c)/2\)
  3. \(s ← sqrt(p*(p-a)*(p-b)*(p-c))\)
  4. \(Output \ s\)
#include<cstdio>
#include<cmath>
int main()
{
	double a,b,c,p,s;
	scanf("%lf%lf%lf",&a,&b,&c);
	p = (a+b+c)/2;
	//表达式中*不能省略
	s = sqrt(p*(p-a)*(p-b)*(p-c));
	printf("%.2lf",s);
	return 0;
}

注意,实际编程时需要考虑代码的可读性,例如下面的代码虽然更加精简,但是阅读起来会有点费解:

#include<cstdio>
#include<cmath>
int main()
{
	//不建议编写如下“看似精简,但是可读性不高的代码”
	double a,b,c;
	scanf("%lf%lf%lf",&a,&b,&c);
	printf("%.2lf",sqrt((a+b+c)/2*((a+b+c)/2-a)*((a+b+c)/2-b)*((a+b+c)/2-c)));
    return 0;
}

2.数的拆分——求逆序数

问题描述:求一个三位正整数的逆序数,所谓逆序说就是将整数逆序书写得到的整数,例如123的逆序数是321;120的逆序数是21(去掉高位多余的0)

输入格式:一个三位正整数n

输出格式:输出n的逆序数,逆序数高位不能有多余的0

问题分析:将三位正整数 \(n\) 每位上的数字“拆分”出来,个位、十位、百位上的数字分别存储到变量 \(g、s、b\) 中,然后依次输出\(g、s、b\),不过不用任何符号隔开,那么看上去就是一个三位数,也就是要求的逆序数。那么问题只剩下如何“拆分”了:来看两个运算 \(\%\)和\(/\) ,\(n\%10\)的结果就是个位上的数字\(g\);要计算十位上的数字\(b\),先来看\(n/10\)(整数/整数结果是整数!)的结果是把\(n\)的个位数“砍掉”了,原来十位上的数到了结果的个位数上,那么结果%10,也就是\(n\ / 10\ \%\ 10\)就是十位上的数字\(s\);同样的\(n\ / \ 100\ \%\ 10\)(对于三位数来说\(n/100\)也行)就是百位上的数字\(b\)。

算法设计:

  1. \(Input \ n\)
  2. \(g \ ← \ n \% 10\)
  3. \(s \ ← \ n/10 \% 10\)
  4. \(b \ ← \ n/100 \% 10\)
  5. \(Output \ g,s,b\)(不用任何符号隔开)
#include<iostream>
using namespace std;
int main()
{
    int n,g,s,b;
    cin>>n;
    g = n%10;
    s = n/10%10;
    b = n/100%10;
    cout<<g<<s<<b;
    return 0;
} 

程序运行测试情况如下:

输入123,输出:

321

输入120,输出:

021

输入100,输出:

001

分析测试结果,可知右边两种情况(个位是0或者除百位外全是0),直接输出拆分出来的个、十、百位数的数字,不能满足题目“逆序数高位不能为0”的要求。那么我们设计的算法还不能全面地解决问题,需要进行调整。其实拆出来\(g、s、b\)后,可以再重新组合成新整数:\(g*100+s*10+b\),这也是 \(n\) 的逆序数,并且这样做可以自动去掉高位多余的0。我们可以自行修改代码,并再次进行全面的测试。

其实这个问题还可以利用scanf("%1d%1d%1d",&b,&s,&g);的方式输入数据,或者使用字符方式输入数据,这样就不用拆分整数了。

#include<cstdio>
int main()
{
    int n,g,s,b;
    scanf("%1d%1d%1d",&b,&s,&g);
    n = g*100+s*10+b;
    printf("%d",n);
    return 0;
} 
#include<cstdio>
int main()
{
    int n;
	char g,s,b;
    scanf("%c%c%c",&b,&s,&g);
    //b,s,g都是数字字符,减去'0'就是对应的整数 
    n = (g-'0')*100+(s-'0')*10+(b-'0');
    printf("%d",n);
    return 0;
}

3.[洛谷]P1425 小鱼的游泳时间

初步分析:游泳小时数为\(c-a\),分钟数为\(d-b\)

算法设计:

  1. \(Input \ a,b,c,d\)
  2. \(Output \ c-a\)
  3. \(Output \ d-b\)
#include<iostream>
using namespace std;
int main()
{
	int a,b,c,d;
	cin>>a>>b>>c>>d;
	cout<<c-a<<" "<<d-b;
	return 0;
}

测试结果如下:

输入数据:

8 10 12 20

输出数据:

4 10

输入数据:

12 50 19 10

输出数据:

7 -40

从测试结果可知,算法有问题!原因是没有考虑\(d<b\)的情况,此时是不能直接用\(d-b\)作为分钟数(不用担心\(a,c\)的大小情况,合法的输入数据肯定满足\(a \le c\))。看来需要重新设计算法。计算00:00到开始时间a时b分的总分钟数:\(t1 = a*60+b\),00:00到结束时间c时d分的总分钟数:\(t2 = c*60+d\),那么游泳持续时间(分钟数)\(t = t2-t1\),换算成小时数为\(t/60\),分钟数为\(t\%60\)。

#include<iostream>
using namespace std;
int main()
{
	int a,b,c,d,t;
	cin>>a>>b>>c>>d;
	t = (c*60+d)-(a*60+b);
	cout<<t/60<<" "<<t%60;
	return 0;
}

4.[洛谷]P1421 小玉买文具

问题分析:和上一题类似,这里有元、角2个单位,可以先统一单位,例如全部转换成角,那直接做除法就行(整数/整数结果是整数!)

#include<iostream>
using namespace std;
int main()
{
	int a,b;
	cin>>a>>b;
	cout<<(a*10+b)/19;
	return 0;
} 

转换成角来计算,计算简单。

#include<iostream>
using namespace std;
int main()
{
	int a,b,ans;
	cin>>a>>b;
	ans = (a+0.1*b)/1.9;
	cout<<ans;
	return 0;
} 

转换成元来计算,计算复杂(除法结果为浮点数,需要转换成整数)。本例因为小数位数仅有一位,浮点数除法计算在可靠范围。但是有些问题用浮点数计算就可能不可靠了。记住:能够避免浮点运算尽量避免!

5.[洛谷]P5709 Apples Prologue

问题分析:总时间为\(s\)分钟,吃一个苹果花费\(t\)分钟,那么吃掉的苹果数应该是\(s/t\),要计算剩下的完整苹果数量,而\(s/t\)结果只是除法的整数部分,已经吃过的苹果(包括没有吃完的)的数量是\((int)ceil(1.0*s/t)\)(\(ceil\)向上取整,结果是浮点数,还需要int强制类型转换),剩余苹果数量是\(m-(int)ceil(1.0*s/t)\)。

#include<iostream>
#include<cmath>
using namespace std;
int main()
{
	int m,t,s;
	cin>>m>>t>>s;
	cout<<m-(int)ceil(1.0*s/t);
	return 0;
} 

在洛谷平台提交后,评测结果如下:

三个点错误。鼠标放到错误点上,显示了错误原因:Wrong Answer.wrong answer On line 1 column 1.read -,except 0.


测评的过程是这样的:每个测试点都设计了标准的输入数据和对应的标准输出答案。测评时会将提交的程序编译后去“跑”每个测试点的输入数据,程序的结果和测试点的标准结果对比,如果相同则认为该点通过,否则未通过。

这里给出的错误原因指出读到程序输出的第1行(line 1)第1列(column 1)内容是 -,但是该处期望(expected,也就是标准答案)的内容是0。

注意错误提示不会提示大片区域错误,只会提示在某行某列处第一次匹配不上(内容不同),例如程序运行结果是123,但是标准答案是136,那么只会提醒第一处不匹配的内容,也就是 line 1 column 2,此时错误信息可能是:Wrong Answer.wrong answer On line 1 column 2.read 2,except 3.

此外,测评系统匹配程序输出结果和标准答案时,对于每一行末尾的空格,以及整个内容后的空行,一般都会忽略不计(包括洛谷平台、竞赛时的测评)。例如题目要求输出1行,我们使用cout<<结果<<endl;cout<<结果;都没有问题。


结合题目和错误提示信息,可以推测应该是\(m-(int)ceil(1.0*s/t)\)的计算结果出现了负数(提示信息给出“在第一行第一列读到-”的原因),这个时候按照题意,应该输出0。结果应该取\(m-(int)ceil(1.0*s/t)\)和0的最大值,可以借助\(max\)函数实现(后续我们还可以通过选择结构的if语句实现):

#include<iostream>
#include<cmath>
using namespace std;
int main()
{
	int m,t,s;
	cin>>m>>t>>s;
	cout<<max(0,m-(int)ceil(1.0*s/t));
	return 0;
}