第1部分 语言篇(笔记)

 

[主程序]:

#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
实验 6
printf("%.1f\n", 8/5);
输出:0.0
实验 7
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 即可。

[1-7]:

[主程序三位数反转(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
 

[1-13三整数排序(2]:

[主程序]:

#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
程序如下:

[1-14三整数排序(3]:

​​​​​​​

[主程序]:

#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 :可以用花括号把若干条语句组合成一个整体。这些语句仍然按顺序执行。

[1-4]:

[主程序]:

[运行输出]:

[1-4]:

[主程序]:

[运行输出]:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值