[主程序]:
#include<stdio.h>
int main()
{
[可更换部分]
return 0;
}
[知识点]:
%d\n =
%.1f\n = 保留小数点后1位
提示
1-1
:整数值用
%d
输出,实数用
%f
输出。
提示
1-2
:整数
/
整数
=
整数,浮点数
/
浮点数
=浮点数。
整数-浮点数=浮点数 (整数先
“
变
”
成浮点数,然后浮点数
-
浮点数
=
浮点数)
这条规则同样适用于加法、减法和乘法,不过没有除法这么容易出错——毕竟整数乘
整数的结果本来就是整数。
提示
1-3
:
scanf
中的占位符和变量的数据类型应一一对应,且每个变量前需要加
“&”
符号。
int型变量存放整数值,而double
型变量存放浮点数值(专业的说法是“双精度
”
浮点数)
提示
1-4:在算法竞赛中,输入前不要打印提示信息。输出完毕后应立即终止程序,不要等待用户按键,因为输入输出过程都是自动的,没有人工干预。 在一般情况下,你的程序不能直接读取键盘和控制屏幕:不要在算法竞赛中使用 getch()、
getche()
、
gotoxy()
和
clrscr()
函数(早期的教材中可能会介绍这些函数)。
首先,选手程序的执行是自动完成的,没有人工干预。不要在用户输入之前打印提示信
息(例如
“Please input n:”
),这不仅不会为程序赢得更高的
“
界面友好分
”
,反而会让程序丢
掉大量的(甚至所有的)分数
——
这些提示信息会被当作输出数据的一部分。例如,刚才的
程序如果加上了
“
友好提示
”
,输出信息将变成:
Please input n:
Area = 274.889
比标准答案多了整整一行!
其次,不要让程序
“
按任意键退出
”
(例如,调用
system("pause")
,或者添加一个多余的
getchar()
),因为不会有人来
“
按任意键
”
的。不少早期的
C
语言教材会建议在程序的最后添加
这样一条语句来
“
观察输出结果
”
,但注意千万不要在算法竞赛中这样做。
提示
1-5
:在算法竞赛中不要使用头文件
conio.h
,包括
getch()
、
clrscr()
等函数。
最后,最容易忽略的是输出的格式:在很多情况下,输出格式是非常严格的,多一个或
者少一个字符都是不可以的!
提示
1-6
:在算法竞赛中,每行输出均应以回车符结束,包括最后一行。除非特别说
明,每行的行首不应有空格,但行末通常可以有多余空格。另外,输出的每两个数或者字符
串之间应以单个空格隔开。
总结一下,算法竞赛的程序应当只做
3
件事情:读入数据、计算结果、打印输出。不要
打印提示信息,不要在打印输出后
“
暂停程序
”
,更不要尝试画图、访问网络等与算法无关的
任务。
提示
1-7
:尽量用
const
关键字声明常数。
提示
1-8
:赋值是个动作,先计算右边的值,再赋给左边的变量,覆盖它原来的值。
提示
1-9
:
printf
的格式字符串中可以包含其他可打印符号,打印时原样输出。
提示
1-10
:算法竞赛的题目应当是严密的,各种情况下的输出均应有严格规定。如果
在比赛中发现题目有漏洞,应向相关人员询问,尽量不要自己随意假定。
提示
1-11
:赋值
a=b
之后,变量
a
原来的值被覆盖,而
b
的值不变。
[可更换部分]:
【1-1】
printf("%d\n", 1+2);
输出:3
【1-2】
printf("%.1f\n", 8.0/5.0);
输出:1.6
实验
5
:
printf("%.2f\n", 8.0/5.0);
输出:1.60
printf("%.1f\n", 8/5);
输出:0.0
printf("%d\n", 8.0/5.0);
输出:1382426792
实验
5
并不难解决,但实验
6
和实验
7
的答案就很难简单解释了
——
真正原因涉及整数和
浮点数编码,相信多数初学者对此都不感兴趣。原因并不重要,重要的是规范:根据规范做
事情,则一切尽在掌握中。
[主程序]:
#include<stdio.h>
#include<math.h>
int main()
{
[可更换部分]
return 0;
}
[可更换部分]:
【1-3】
printf("%.8f\n", 1+2*sqrt(3)/(5-0.1));
输出:1.70695951
[1-4]:
[主程序]:
#include <stdio.h>
int main()
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", a+b);
return 0;
}
1
2
3
输入:
1
2
输出:
3
2
3
5
5
5
10
[1-5]:
输入底面半径
r
和高
h
,输出圆柱体的表面积,保留
3
位小数,格式见样例。
样例输入:
3.5 9
样例输出:
Area = 274.889
【分析】
圆柱体的表面积由
3
部分组成:上底面积、下底面积和侧面积。由于上下底面积相等,
完整的公式可以写成:表面积
=
底面积
×2+
侧面积。根据几何知识,底面积
=
πr
2
,侧面积
=2
πrh
。不难写出完整程序:
const double pi = acos(-1.0); => π
回到刚才的程序,它多了几个新内容。首先是
“const double pi = acos(-1.0);”
。这里也声
明了一个叫
pi
的
“
符号
”
,但是
const
关键字表明它的值是不可以改变的
——pi
是一个真正的数
学常数。
最后是“Area = %.3f\n”,该语句的用法很容易被猜到:只有以
“%”
开头的部分才会被后面的值替换掉,其他部分原样输出。
[主程序三位数反转(1)]:
#include<stdio.h>
#include<math.h>
int main()
{
const double pi = acos(-1.0);
double r, h, s1, s2, s;
scanf("%lf%lf", &r, &h);
s1 = pi*r*r;
s2 = 2*pi*r*h;
s = s1*2.0 + s2;
printf("Area = %.3f\n", s);
return 0;
}
[运行输出]:
/tmp/0UKI7MzIkL.o
3.5
9
Area = 274.889
[1-6]:
输入一个三位数,分离出它的百位、十位和个位,反转后输出。
样例输入:
127
样例输出:
721
【分析】
首先将三位数读入变量
n
,然后进行分离。百位等于
n
/100
(注意这里取的是商的整数部
分),十位等于
n
/10%10
(这里的
%
是取余数操作),个位等于
n%10
。程序如下:
[主程序]:
#include<stdio.h>
int main()
{
int n;
scanf("%d", &n);
printf("%d%d%d\n", n%10, n/10%10, n/100);
return 0;
}
[运行输出]:
/tmp/0UKI7MzIkL.o
123
321
/tmp/0UKI7MzIkL.o
987
789
/tmp/0UKI7MzIkL.o
591
195
此题有一个没有说清楚的细节,即:如果个位是
0
,反转后应该输出吗?例如,输入是
520
,输出是
025
还是
25
?如果在算法竞赛中遇到这样的问题,可向监考人员询问
(4)
。但是在
这里,两种情况的处理方法都应学会。
上面的程序输出
025
,但要改成输出
25
似乎会比较麻烦
——
必须判断
n%10
是不是
0
,但
目前还没有学到
“
根据不同情况执行不同指令
”
(分支结构程序设计是
1.4
节的主题)。
一个解决方法是在输出前把结果存储在变量
m
中。这样,直接用
%d
格式输出
m
,将输出
25
。要输出
025
也很容易,把输出格式变为
%03d
即可。
[主程序三位数反转(2)]:
#include<stdio.h>
int main()
{
int n, m;
scanf("%d", &n);
m = (n%10)*100 + (n/10%10)*10 + (n/100);
printf("%03d\n", m);
return 0;
}
[运行输出]:
/tmp/0UKI7MzIkL.o
520
025
/tmp/0UKI7MzIkL.o
102
201
/tmp/0UKI7MzIkL.o
990
099
[1-8]:
输入两个整数
a
和
b
,交换二者的值,然后输出。
样例输入:
824 16
样例输出:
16 824
【分析】
按照题目所说,先把输入存入变量
a
和
b
,然后交换。如何交换两个变量呢?最经典的方
法是三变量法:
[主程序变量交换(1)]:
#include<stdio.h>
int main()
{
int a, b, t;
scanf("%d%d", &a, &b);
t = a; a = b; b = t;
printf("%d %d\n", a, b);
return 0;
}
[运行输出]:
/tmp/0UKI7MzIkL.o
3
6
6 3
/tmp/0UKI7MzIkL.o
230
60
60 230
/tmp/0UKI7MzIkL.o
8882138
222
222 8882138
[1-9]:
[主程序变量交换(2)]:
#include<stdio.h>
int main()
{
int a, b;
scanf("%d%d", &a, &b);
a = a + b;
b = a - b;
a = a - b;
printf("%d %d\n", a, b);
return 0;
}
[运行输出]:
/tmp/0UKI7MzIkL.o
3
6
6 3
/tmp/0UKI7MzIkL.o
230
60
60 230
/tmp/0UKI7MzIkL.o
232131
88
88 232131
这次就不太方便用倒酱油做比喻了:硬着头皮把醋倒在酱油瓶子里,然后分离出酱油倒
回醋瓶子?比较理性的方法是手工模拟这段程序,看看每条语句执行后的情况。
在顺序结构程序中,程序一条一条依次执行。为了避免值和变量名混淆,假定用户输入
的是
a
0
和
b
0
,因此
scanf
语句执行完后
a
=
a
0
,
b
=
b
0
。
执行完
a
=
a
+
b
后:
a
=
a
0
+
b
0
,
b
=
b
0
。
执行完
b
=
a
-
b
后:
a
=
a
0
+
b
0
,
b
=
a
0
。
执行完
a
=
a
-
b
后:
a
=
b
0
,
b
=
a
0
。
这样,就不难理解两个变量是如何交换的了。
提示
1-12
:可以通过手工模拟的方法理解程序的执行方式,重点在于记录每条语句执
行之后各个变量的值。
这个方法看起来很好(少用一个变量),但实际上很少使用,因为它的适用范围很窄:
只有定义了加减法的数据类型才能采用此方法
。事实上,笔者并不推荐读者采用这样的技
巧实现变量交换:三变量法已经足够好,这个例子只是帮助读者提高程序阅读能力。
[1-10变量交换(3)]:
提示
1-14
:算法竞赛是在比谁能更好地解决问题,而不是在比谁写的程序看上去更高
级。
换句话说,我们的目标是解决问题,而不是为了写程序而写程序,同时应保持简单
(
Keep It Simple and Stupid
,
KISS
),而不是自己创造条件去展示编程技巧。
[主程序]:
#include<stdio.h>
int main()
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d %d\n", b, a);
return 0;
}
[运行输出]:
/tmp/0UKI7MzIkL.o
44
55
55 44
/tmp/0UKI7MzIkL.o
888
11
11 888
/tmp/0UKI7MzIkL.o
9422
000
0 9422
[1-11鸡兔同笼]:
已知鸡和兔的总数量为
n
,总腿数为
m
。输入
n
和
m
,依次输出鸡的数目和兔的数目。如
果无解,则输出
No answer
。
样例输入:
14 32
样例输出:
12 2
样例输入:
10 16
样例输出:
No answer
【分析】
设鸡有
a
只,兔有
b
只,则
a
+
b
=
n
,
2
a
+
4
b
=
m
,
联立解得
a
=(
4
n
-
m
)/2,
b
=
n
-
a
。
在什么情况下此解
“
不算数
”
呢?首先,
a
和
b
都是整数;其次,
a
和
b
必须是非负的。可以通过
下面的程序判断:
[主程序]:
#include<stdio.h>
int main()
{
int a, b, n, m;
scanf("%d%d", &n, &m);
a = (4*n-m)/2;
b = n-a;
if(m % 2 == 1 || a < 0 || b < 0)
printf("No answer\n");
else
printf("%d %d\n", a, b);
return 0;
}
[运行输出]:
/tmp/0UKI7MzIkL.o
14
32
12 2
上面的程序用到了
if
语句,其一般格式是:
if(
条件
)
语句
1;
else
语句
2;
注意语句
1
和语句
2
后面的分号,以及
if
后面的括号。
“
条件
”
是一个表达式,当该表达式
的值为
“
真
”
时执行语句
1
,否则执行语句
2
。另外,
“else
语句
2”
是可以省略的。语句
1
和语句
2
前面的空行是为了让程序更加美观,并不是必需的,但强烈推荐读者使用。
提示
1-15
:if语句的基本格式为:if(条件)语句
1
;
else
语句
2
。
换句话说,“
”是一个表达式,其字面意思是“m是奇数,或者
a
小于 0,或者b
小于
0”
。这句话可能正确,也可能错误。因此这个表达式的值可能为真,也可能为
假,取决于
m
、
a
和
b
的具体数值.
这样的表达式称为逻辑表达式。和算术表达式类似,逻辑表达式也由运算符和值构成,
例如
“||”
运算符称为
“
逻辑或
”
,
a||b
表示
a
为真,或者
b
为真。换句话说,
a
和
b
只要有一个为
真,
a||b
就为真;如果
a
和
b
都为真,则
a||b
也为真。和其他语言不同的是,在
C
语言中单个整数
也可以表示真假,其中
0
为假,其他值为真。
提示
1-16
:if语句的条件是一个逻辑表达式,它的值可能为真,也可能为假。单个整数值也可以表示真假,其中0
为假,其他值为真。
细心的读者也许发现了,如果
a
为真,则无论
b
的值如何,
a||b
均为真。换句话说,一旦
发现
a
为真,就不必计算
b
的值。
C
语言正是采取了这样的策略,称为短路(
short-circuit
)。
也许读者会觉得,用短路的方法计算逻辑表达式的唯一优点是速度更快,但其实并不是这
样,稍后将通过几个例子予以证实。
提示
1-17
:
C
语言中的逻辑运算符都是短路运算符。一旦能够确定整个表达式的值,就
不再继续计算。
[1-12三整数排序 (1)]:
例题
1-5
三整数排序
输入
3
个整数,从小到大排序后输出。
样例输入:
20 7 33
样例输出:
7 20 33
【分析】
a
、
b
、
c
这
3
个数一共只有
6
种可能的顺序:
abc
、
acb
、
bac
、
bca
、
cab
、
cba
,所以最简单
的思路是使用
6
条
if
语句。
程序
1-12
三整数排序(
1
)(错误)
#include<stdio.h>
int main()
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(a < b && b < c)printf("%d %d %d\n", a, b, c);
if(a < c && c < b)printf("%d %d %d\n", a, c, b);
if(b < a && a < c)printf("%d %d %d\n", b, a, c);
if(b < c && c < a)printf("%d %d %d\n", b, c, a);
if(c < a && a < b)printf("%d %d %d\n", c, a, b);
if(c < b && b < a)printf("%d %d %d\n", c, b, a);
return 0;
}
上述程序看上去没有错误,而且能通过题目中给出的样例,但可惜有缺陷:输
入
“111”
将得不到任何输出!这个例子说明:即使通过了题目中给出的样例,程序仍然可能
存在问题。
提示
1-18
:算法竞赛的目标是编程对任意输入均得到正确的结果,而不仅是样例数
据。
将程序稍作修改:把所有的小于符号
“
<
”
改成小于等于符号
“
<=
”
(在一个小于号后添
加一个等号)。这下总可以了吧?很遗憾,还是不行。对于
“111”
,
6
种情况全部符合,程序
一共输出了
6
次
“111”
。
一种解决方案是人为地让
6
种情况没有交叉:把所有的
if
改成
else if
。
#include<stdio.h>
int main()
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(a < b && b < c)printf("%d %d %d\n", a, b, c);
if(a < c && c < b)printf("%d %d %d\n", a, c, b);
if(b < a && a < c)printf("%d %d %d\n", b, a, c);
if(b < c && c < a)printf("%d %d %d\n", b, c, a);
if(c < a && a < b)printf("%d %d %d\n", c, a, b);
if(c < b && b < a)printf("%d %d %d\n", c, b, a);
return 0;
}
[运行输出]:
/tmp/0UKI7MzIkL.o
6
1
2
1 2 6
/tmp/0UKI7MzIkL.o
20
7
33
7 20 33
/tmp/0UKI7MzIkL.o
1
1
1
[主程序]:
#include<stdio.h>
int main()
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
if(a <= b && b <= c) printf("%d %d %d\n", a, b, c);
else if(a <= c && c <= b) printf("%d %d %d\n", a, c, b);
else if(b <= a && a <= c) printf("%d %d %d\n", b, a, c);
else if(b <= c && c <= a) printf("%d %d %d\n", b, c, a);
else if(c <= a && a <= b) printf("%d %d %d\n", c, a, b);
else if(c <= b && b <= a) printf("%d %d %d\n", c, b, a);
return 0;
}
[运行输出]:
/tmp/U4hMliDpoB.o
1
1
1
1 1 1
/tmp/tmLWeFDR7n.o
986
6555
222222
986 6555 222222
/tmp/tmLWeFDR7n.o
20
7
33
3 7 20
最后一条语句还可以简化成单独的
else
(想一想,为什么),不过,幸好程序正确了。
提示
1-19
:如果有多个并列、情况不交叉的条件需要一一处理,可以用
else if
语句。
另一种思路是把
a
、
b
、
c
这
3
个变量本身改成
a
≤
b
≤
c
的形式。首先检查
a
和
b
的值,如
果
a
>
b
,则交换
a
和
b
(利用前面讲过的三变量交换法);接下来检查
a
和
c
,最后检查
b
和
c
,
程序如下:
[主程序]:
#include<stdio.h>
int main()
{
int a, b, c, t;
scanf("%d%d%d", &a, &b, &c);
if(a > b) { t = a; a = b; b = t; } //执行完毕之后a≤b
if(a > c) { t = a; a = c; c = t; } //执行完毕之后a≤c,且a≤b依然成立
if(b > c) { t = b; b = c; c = t; }
printf("%d %d %d\n", a, b, c);
return 0;
}
[运行输出]:
为什么这样做是对的呢?因为经过第一次检查以后,必然有
a
≤
b
,而第二次检查以
后
a
≤
c
。由于第二次检查以后
a
的值不会变大,所以
a
≤
b
依然成立。换句话说,
a
已经是
3
个数
中的最小值。接下来只需检查
b
和
c
的顺序即可。值得一提的是,上面的代码把上述推理写入
注释,成为程序的一部分。这不仅可以让其他用户更快地搞懂你的程序,还能帮你自己理清
思路。在
C
语言中,单行注释从
“//”
开始直到行末为止;多行注释用
“/*”
和
“*/”
包围起来
(6)
。
提示
1-20
:适当在程序中编写注释不仅能让其他用户更快地搞懂你的程序,还能帮你
自己理清思路。
注意上面程序中的花括号。前面讲过,
if
语句中有一个
“
语句
1”
和可选的
“
语句
2”
,且都
要以分号结尾。有一种特殊的
“
语句
”
是由花括号括起来的多条语句。这多条语句可以作为一
个整体,充当
if
语句中的
“
语句
1”
或
“
语句
2”
,且后面不需要加分号。当然,当
if
语句的条件满
足时,这些语句依然会按顺序逐条执行,和普通的顺序结构一样。
提示
1-21
:可以用花括号把若干条语句组合成一个整体。这些语句仍然按顺序执行。