算法初步笔记之顺序结构程序设计

算法笔记(二)


2.1 变量的数据类型

C++语言有很多变量类型,能够存储的数据精度和范围都不一样,所以要根据数据的实际情况选择合适的变量类型。

一个简单的例子

#include<iostream>
using namespace std;
int main (){
	int v_a = 5, v_b = 8, distance = 100; // 小A和小B的速度,以及他们之间的距离
	double delta, ans; // 二者速度的差值和答案
	delta = v_b - v_a; // 两者之间的相对速度,也就是每秒距离缩短多远
	ans = distance / delta; // ans = 1.0 * distance / (v_b - v_a);
	cout << ans <<endl;
	return 0;
}

输出结果
对这个程序进行分析:
最开始定义了v_a, v_b, distance 三个int类型的变量并赋初始值;定义了delta, ans 两个double类型的变量,但并没有给它们赋初始值。
值得注意的是,如果一个在函数内(当然包括主函数main)的变量没有被初始化,就不能拿来参与计算,但是可以被赋值。因此,在使用一个变量之前,应该先给这个变量赋初值。
正常情况下,等号右边的数据类型(整数或者是浮点数)应该和左边变量的数据类型相匹配。当不一致时,会出现自动类型转换。程序的第6行,等号右边是两个整数的差,结果还是整数;但是左边的变量是浮点数类型,所以右边的整数类型会被自动转换成左边的浮点数类型。相反,如果右边是浮点数类型,左边是整数类型,C++在自动类型转换时,会舍去浮点数的小数部分,这说明跨类型的自动转换可能会造成一些精度损失。

常见的数据类型

数据类型占用空间取值范围
char1字节,8位-128~127
int4字节,32位-231~231-1,大约能够表示绝对值不超过2.1x109的整数
unsigned int4字节,32位0~232-1,大约能够表示不超过4.2x109的非负整数
long long8字节,64位-263~263-1,大约能够表示绝对值不超过9.2x1018的整数
unsigned long long8字节,64位0~264-1,大约能够表示不超过1.8x1019的非负整数
float4字节,32位大约指数绝对值不超过37,6位有效数字
double8字节,64位大约指数绝对值不超过307,15位有效数字

取值范围中的“大约”是保守的估计,有时候即使稍微超过也能表示(也有可能不能),但是不超过这里的范围是可以确保准确的。另外可以看出,这些数据类型默认是带符号的(也就是signed),可以表示正数、负数或0。如果在数据类型前面加上unsigned,就会变成无符号数;只能表示0或者正数,不过能够表示的正数范围相对于带符号数扩大了一倍。

权衡精度和空间与速度,一般情况下使用int数据类型来存放整数。只有当int无法存下特别大的数字时,才会去使用long long;而浮点数如果因为空间有限制时选择float,否则选择double。
值得注意的是long long可以表示更大范围的数字,但并不能只使用这些大范围的数据类型而不使用其他类型。这是因为一方面,计算机内存大小是有限的;另一方面,对于一般的运算来说,大范围的数据类型运算速度比较慢。

2.2 英文字母

英文有26个字母,其中A是第一个字母。请问:1)M是字母表中的第几个字母?
2)第18个字母是什么?
输出两个数字,使用换行隔开。

#include<iostream>
using namespace std;
int main () {
	int ans1;
	char ans2;
	ans1 = 'M' - 'A' + 1;
	ans2 = 'A' + 18 - 1;
	cout << ans1 << endl;
	cout << ans2 << endl;
	return 0;
}

输出结果

char类型更普遍的用法是存储一个字符,包括英文大小写字母、数字和标点符号等(汉字无法使用一个char储存),这些字符和0 ~ 127的数字是一 一对应的。在ASCLL表中,第0 ~ 23个字符是不可见字符,‘0’对应48,‘A’对应65,‘a’对应97。
char类型的本质就是一个不大于127的整数,只是这些整数可以表示一个对应的字符。

2.3 强制类型转换

将半径分别为4和10的两块橡皮泥捏成的球揉成一个正方体,请问:正方体的边长?

#include<iostream>
#include<cmath>
using namespace std;
#define PI 3.141593
int main (){
	int r1 = 4, r2 = 10;
	double V, l;
	V = 4.0 / 3 * PI * (r1 * r1 * r1 + r2 * r2 * r2); // 两块橡皮泥的总体积,即正方体的体积
	l = pow(V, 1.0 / 3); // 使用立方根计算边长,注意这里不能写成1/3,这是因为会被当作整数除法计算为0
	cout << (int)l << endl; // 此处使用强制类型转换,将double类型的值转变为int类型的值输出
	cout << l << endl; // 但l还是double类型
	return 0;
}

输出结果
就本题而言,变量 l 一 开始便可定义为int类型,pow函数计算完后赋值给整型变量 l,就会自动转换成整数,而代价是失去了小数点后的数字。

2.4 销量预测

当课程定价为110元时,会有10人报名。课程价格每降低1元,就会多1名报名者,反之亦然。如果希望总共能收到3500元学费,那么应该定价多少呢?已知本题有两个答案符合要求,则取较小的那一个。如果这个答案不是整数,则需四舍五入精确到整数。

分析:假设课程价格降低x元,那么定价就变为110-x元,报名人数变为10+x人,则有关系式(110-x)(10+x)=3500。经过化简得到方程x2-100x+2400=0。根据二次方程的求根公式 x 1 , 2 = − b ± Δ 2 a x_{1,2}=\frac{-b\pm\sqrt{\mathrm{Δ}}}{2a} x1,2=2ab±Δ Δ = b2 – 4ac。最后取较大的那个根即可,这样定价就是较小的那一个解。

#include<iostream>
#include<cmath>
using namespace std;
int main (){
	double a = 1, b = -100, c = 2400;
	double delta, ans;
	delta = pow(b, 2) - 4 * a * c;
	ans = (-b + sqrt(delta)) / (2 * a);
	cout << 110 - int(ans + 0.5) << endl;  
	return 0;
}

值得注意的是此处int(ans + 0.5)的意思是四舍五入到整数,而不能直接int(ans)这样做的话只是简单直接舍去了小数部分。进行强制转换时,除了像上例那样在类型外面加括号外,也可以在强制转换的对象的外面加上括号,前面加上需要转换的类型名即可。

2.5 变量的输入与输出

例题一 数字反转(洛谷P5705)。输入一个不小于100且小于1000,同时包含小数点后一位的一个浮点数,例如123.4,要求把这个数字翻转过来,变成4.321并输出。

解法1:需要分离出这个数字的所有位数。首先需要乘10把这个数字变成一个4位数整数。然后把这个数字模10取余数,就可以获得这个4位数的个位数字。可以通过类似的办法获得这个4位数的其他位数。

#include<iostream>
using namespace std;
int main (){
	double p; // 输入的数字
	int q, a, b, c, d; // 转换成的4位数和分离出来的4位数字
	cin >> p;
	q = int(p * 10);
	a = q / 1000; // 百位
	b = q / 100 % 10; // 十位 
	c = q / 10 % 10; // 个位
	d = q % 10; // 小数位
	cout << d << "." << c << b << a << endl;
	return 0; 
}

解法2:将输入视为5个字符,然后直接输出。

#include<iostream>
using namespace std;
int main (){
	char a, b, c, dot, d;
	cin >> a >> b >> c >> dot >> d;
	cout << d << dot << c << b << a << endl;
	return 0;
}

这里依次读入了5个字符。既然输入数字的时候中间必须要有空格或者换行来分割两个数字,那么为什么读入字符时候不用呢?这是因为一个char只能容纳一个字符,所以不需要额外分割。

解法3:可以使用C语言风格的输入输出。

#include<cstdio>
using namespace std; // 因为没有使用到C++语言的特性,这条语句也可省略
int main (){
	char a, b, c, d;
	scanf("%c%c%c.%c", &a, &b, &c, &d);
	printf("%c.%c%c%c", d, c, b, a);
	return 0;
}

需要在注意的是头文件变成了#include< cstdio >,这个头文件包含了下面要使用的scanf和printf函数。

例题二 再分肥宅水(洛谷P5706)。现在有 t 毫升肥宅快乐水,要均分给 n 名同学。每位同学需要两个杯子。想知道每位同学可以获得多少毫升饮料(严格精确到小数点后3位),以及一共需要多少个杯子。输入一个实数 t 和一个整数 n,使用空格隔开。输出两个数字表示答案,使用换行隔开。

#include<cstdio>
using namespace std;
int main (){
	double t;
	int n;
	scanf("%lf%d", &t, &n);
	printf("%.3f\n%d", t / n, n * 2);
	return 0;
}

%lf 表示接受一个 double 类型的占位符,而 %d 表示接受一个 int 类型的占位符。这两个数据必须使用空格或者换行分隔开才能正确读入。需要注意的是,输出 double 或者 float 都应当使用 %f。包含在字符串中的 \n 表示一个换行符,注意这里不能用endl来表示换行了。

常见的输入输出占位符

占位符说明
%d一个十进制整数,一般用于 int 类型(最常用)
%nd(n 是正整数)输出一个整数,如果不足n位,前面用空格补齐直到n位
%l64d(Windows)%lld(Linux)一个十进制整数,一般用于 long long 类型。注意在不同的系统下,这个占位符是不一样的
%f读入一个 float 类型的带小数点的浮点数,或者输出 float 或者 double 类型的浮点数,默认6位小数
%lf读入 double 类型的浮点数
%.nf(n 是正整数)用于输出一个固定 n 位小数的浮点数
%0nd(n 是正整数)输出一个整数,如果不足 n 位,前面用0补齐直到够 n 位
%c一个 char 类型的字符
%s一个字符串

当然,也可以使用 cout 输出,配合使用 fixed 和 setprecision() 可以达到保留指定位数小数的效果。在 cout 语句中,也可以用 \n 来代替 endl,但二者在本质上不同。一般来说,使用scanf读入同样数据的速度要快于cin,而且printf还能通过使用占位符来支持更加丰富的格式化。大多数的情况下程序的输出数量不会很多,这两种输出方式耗时差异不明显。使用 cin / cout 输入输出时可以不用考虑各种占位符,相对更方便。
此外,还可以通过在程序开始加上一条语句 ios::sync_with_stdio(false); 来关闭 cin 和 scanf 之间的同步,从而加快 cin 的读入速度,但这样做了之后就不能使用 scanf 了。

2.6 顺序结构程序设计案例

例题1:小鱼的游泳时间 (洛谷P1425)。
题目描述
伦敦奥运会要到了,小鱼在拼命练习游泳准备参加游泳比赛,可怜的小鱼并不知道鱼类是不能参加人类的奥运会的。
这一天,小鱼给自己的游泳时间做了精确的计时(本题中的计时都按 24 24 24 小时制计算),它发现自己从 a a a b b b 分一直游泳到当天的 c c c d d d 分,请你帮小鱼计算一下,它这天一共游了多少时间呢?
小鱼游的好辛苦呀,你可不要算错了哦。
输入格式
一行内输入四个整数,以空格隔开,分别表示题目中的 a , b , c , d a, b, c, d a,b,c,d
输出格式
一行内输出两个整数 e e e f f f,用空格间隔,依次表示小鱼这天一共游了多少小时多少分钟。其中表示分钟的整数 f f f 应该小于 60 60 60
样例 #1
样例输入 #1

12 50 19 10

样例输出 #1

6 20

提示
对于全部测试数据, 0 ≤ a , c ≤ 24 0\le a,c \le 24 0a,c24 0 ≤ b , d ≤ 60 0\le b,d \le 60 0b,d60,且结束时间一定晚于开始时间。

分析:这是一个符合大多数算法竞赛格式的题目,选手在训练时接触的题目形式也基本上是这样的。
题目描述是告诉选手需要解决一个什么样的问题,输入格式和输出格式是告知输入数据和输出数据应该是长什么样子。样例输入输出是给选手一个参考,进一步帮助选手理解题意和输入输出格式;有些题目会对样例进行解释,有些样例可以帮助选手检查一些错误。提示部分数据规模限制与说明会告知测试数据的范围以确定解题方法。

#include<cstdio>
using namespace std;
int main (){
	int a, b, c, d, e, f, delta;
	scanf("%d%d%d%d", &a, &b, &c, &d);
	delta = (60 * c + d) - (60 * a + b);
	e = delta / 60;
	f = delta % 60;
	printf("%d %d", e, f);
	return 0;
}

例题2:成绩(洛谷P3954,NOIP 2017 普及组)。
某门课程成绩的计分方法是,总成绩 = 作业成绩 x 20% + 小测成绩 x 30% + 期末考试成绩 x 50%。现在已经知道各项得分,求总成绩。
输入共1行,3个非负整数 A , B , C A,B,C A,B,C 分别表示作业成绩、小测成绩和期末考试成绩。相邻两个数之间用一个空格隔开,三项成绩的满分都是100分。
输出一个数字,表示课程的总成绩,满分也是100分。
输入样例:

60 90 80

输出样例:

79

样例说明:作业成绩是60分,小测成绩是90分,期末考试成绩是80分,总成绩 = 60 x 20% + 90 x 30% + 80 x 50% = 12 + 27 + 40 = 79。
数据规模限制与说明:对于全部测试数据, 0 ≤ A , B , C ≤ 100 0≤A,B,C≤100 0A,B,C100 A , B , C A,B,C A,B,C 都是 10 10 10 的整数倍。

#include<cstdio>
using namespace std;
int main (){
	int a, b, c;
	scanf("%d%d%d", &a, &b, &c);
	printf("%d", a * 2 / 10 + b * 3 / 10 + c * 5 / 10); // 写法1
	// printf("%d", int(a * 0.2 + b * 0.3 + c * 0.5 + 0.5)); // 写法2
}

在上述的两种写法中,第二种写法由于整数乘上一个浮点数后就会变成又一个浮点数,所以最后输出一个整数时需要强制类型转换。其最后面的+0.5的意思是四舍五入。这是因为浮点数的计算容易产生误差,导致结果可能会产生类似于78.9999这样的数字,如果直接强制类型转换后面的小数就会被舍去了。而第一种写法正是注意到了题目在数据规模中提到的 A , B , C A,B,C A,B,C 都是 10 10 10 的整数倍。故每一小项的结果必定是整数,采用写法1避免了浮点数误差。

例题3:上学迟到(洛谷P5707)。
学校和 yyy 的家之间的距离为 s s s 米,而 yyy 以 v v v 米每分钟的速度匀速走向学校。在上学的路上,yyy 还要额外花费 10 10 10 分钟的时间进行垃圾分类。学校要求必须在上午 8:00 \textrm{8:00} 8:00 到达,请计算在不迟到的前提下,yyy 最晚能什么时候出门。由于路途遥远,yyy 可能不得不提前一点出发,但是提前的时间不会超过一天。
输入格式
一行两个正整数 s , v s,v s,v,分别代表路程和速度。
输出格式
输出一个 24 24 24 小时制下的时间,代表 yyy 最晚的出发时间。输出格式为 HH:MM \texttt{HH:MM} HH:MM,分别代表该时间的时和分。必须输出两位,不足前面补 0 0 0
样例 #1
样例输入 #1

100 99

样例输出 #1

07:48

提示
对于 100 % 100\% 100% 的数据, 1 ≤ s , v ≤ 1 0 4 1 \le s,v \le 10^4 1s,v104

#include<cstdio>
#include<cmath>
using namespace std;
int main (){
	int s, v;
	scanf("%d%d", &s, &v);
	int use_time = ceil(1.0 * s / v) + 10;
	int from_zero = 60 * (24 + 8) - use_time; // 到前一天零点的分钟数
	int hh = (from_zero / 60) % 24; // 计算小时
	int mm = from_zero % 60; // 计算分钟
	printf("%02d:%02d\n", hh, mm); // 输出两位,用0补齐
	return 0;
}

此题与前面小鱼的游泳时间解题思路上类似。ceil函数是向上取整,例如ceil(3) = ceil(2.9) = ceil(2.1) = 3。注意这个函数的输入输出都是 double 类型,而 s 和 v 是整数,所以要进行类型转换。

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值