信息学奥赛算法大全
信息学奥赛算法大全
前言
这是一篇博客,也可以说是一本书。这篇博客将带着读者们遨游 OI 的世界。在这里,你可以看到从基本的顺序结构到困难的 A* 算法。我们的学习过程中,需要使用到 Openjudge 和 洛谷 、 Acwing 几种 OJ,请各位读者提前注册好账号。通往 OI 的传送门现在开启!
——DijkstraPhoenix,2023/4/27
一、语法初期
学信奥是必须要学会语法的,语法是将思维变成实现的一种介质,没有语法,任何思维都无法变为现实。
1.1 环境搭建
Windows 环境
在 Windows 环境下,我们使用 Dev-C++ 作为编译器(也就是 IDE),安装方式如下:
打开这个网址,下载安装包: https://sourceforge.net/projects/orwelldevcpp/
运行刚刚下载的安装包,进行下载(一开始的语言选择就选 English 就行了)。安装完后启动 Dev-C++,这里再选择语言为简体中文。字体、颜色、图标自己选,尽量选可以看得清的。
然后就是这个样子:
Linux 环境
我们在 Linux 环境下,没有 Dev-C++,所以,我们选择另一种不错的 IDE —— Code::Blocks。
如果不习惯 Code::Blocks 的话,可以使用 Geany (自行安装)。
在这里,我们仅展示 Debian 内核的 Linux 安装 Code::Blocks 的步骤,其它 Linux 版本请自行查询安装教程。
Tips: Debian 内核的 Linux 有 Kali、Ubuntu、Deepin、Noi linux 等。
我们打开终端,输入以下命令:
1 |
|
此时,我们需要输入当前用户的密码,才能进行安装(如果用的是超级用户的终端,可以不输入 sudo
)。
等待安装完成,我们就可以通过菜单或者在终端里输入 codeblocks
就能启动了。如果无法启动,则可以在终端里输入 sudo codeblocks
或者重装该软件。
1.2 Hello, World!
注:以后我们的代码里会有很多注释,程序不会执行它,如:
1 |
|
任何一本学习编程的书,基本上第一课就是输出 Hello, World!
。
本题请见 Openjudge 1.1.01 - Hello, World!
题目描述
输出一行字符串 “Hello, World!”
输入样例
1 |
|
输出样例
1 |
|
这道题我们可以画一个简单的流程图:
1 |
|
在 C++ 中,任何一个程序都是从主函数执行的,我们只需要将代码写在主函数里就行了。
主函数的定义:
1 |
|
当然,主函数还有其它的定义方式,比如以下几种(此处没有列举完):
1 |
|
不过我们学习信奥不需要这些参数,有兴趣的读者可以自行查阅资料。
因为我们需要输出一些东西,而流输出函数 cout
定义在 iostream
头文件里,所以我们需要导入它。
cout 是流输出函数,它不需要加括号,只需要加上流输出符号,如:cout<<1<<2;
完整代码(注释打不打也没有关系,这里做详细的讲解):
1 |
|
我们在 C++ 中,基本上是兼容 C 语言的代码的,所以,这个代码还可以这么写:
1 |
|
我们把这个程序保存为 Helloworld.cpp
(名字可以自己改,后缀名不要改),运行一下就成功啦!
如果我们用 C语言 写的话,还可以把后缀名改为 .c
。
1.3 变量
我们在编程中,不可避免的需要大量变量,本章我们来讲讲变量。
变量是一种可以储存值的容器,可取值也可写入。
常见的变量有以下几种:
1 |
|
而一些变量名称前面还可以加上修饰符:
1 |
|
不过变量也有取值范围,这里写出几种:
1 |
|
超过变量的取值范围,变量将会顶破符号位(在位运算中会详细讲解),变成一个极小的负数。
在我们编程中,要选择合适的变量类型和名称,否则会造成不必要的错误。
变量的命名规则有以下几点:1.只能由大小写英文字母、数字和下划线组成(可以使用下划线或者大小写来表示空格)。2.不能以数字开头。3.要避免已经定义过的变量,不管是什么类型的,而且不能定义 C++ 自带的标识符(例如 cin、cout、printf 等)。
变量也可以互相运算,在 C++ 里,运算符分别是这样的:
1 |
|
而变量的变化方式也有几种:
1 |
|
1.4 顺序结构
A+B Problem
题目描述
输入两个整数 a,b ,输出它们的和 ( |a|,|b| <= 1e9 )。
输入样例
1 |
|
输出样例
1 |
|
对于这道题,我们需要定义两个变量,来保存 a 和 b 的值,输入这两个变量,在输出它们的和就行了。
所以,我们可以写出这样的代码:
1 |
|
输出第2个整数
题目描述
输入三个整数,整数之间由一个空格分隔。把第二个输入的整数输出。
输入样例
1 |
|
输出样例
1 |
|
本题我们需要定义三个变量,分别输入后再输出第二个变量的值,代码如下:
1 |
|
课后习题
提示:保留3位小数可以用 printf("%.3f",float类型变量);
,double类型需要是 %.3lf
1.5 分支结构
奇偶数判断
题目描述
给定一个整数 n,判断该数是奇数还是偶数。如果 n 是奇数,输出 odd
;如果 n 是偶数,输出 even
。
输入样例
1 |
|
输出样例
1 |
|
这道题我们需要进行一个判断:如果(n是奇数) 输出”odd”;否则 输出”even”。
而 C++ 里的判断语句,我们可以使用 “if……else if……else” 语句,if 后面的括号里是填判断的依据。
所以,代码可以这么写:
1 |
|
输出绝对值
题目描述
输入一个浮点数,输出这个浮点数的绝对值。
输入格式
输入一个浮点数,其绝对值不超过10000。
输出格式
输出这个浮点数的绝对值,保留到小数点后两位。
样例输入
1 |
|
样例输出
1 |
|
这道题存在一个使用库函数 fabs()
的做法,在这里不做讲解,有兴趣的读者自行查阅。
绝对值是指一个数到 0 的距离,例如 | 123 | = 123, | -456 | = 456。
不难看出,正数和 0 的绝对值就是它本身,而负数的绝对值就是对它取反。
所以,本题可以分类列举大于等于 0 和小于 0 两种情况。
代码如下:
1 |
|
其中的三目运算符 cout<<(x>=0?x:-x);
和这段代码等同:
1 |
|
逻辑运算
常见的逻辑运算有与、或、非三种。
与的两边同时成立,则表达式成立;或的两边只要有一个是成立的,则表达式成立;非的后面是成立的,则表达式不成立,否则成立。
它们分别写作 &&
、 ||
、 !
,写在 if 语句里就会变成这样:
1 |
|
课后习题
Tips:此题需要使用到逻辑运算。
1.6 循环结构
我们在编程中,总会需要循环,虽然一些可以不停地写同一行代码,但遇到无限循环或者不定次数循环就无能为力了。
常见的循环有三种:while
循环、do···while
循环和 for
循环。
首先说说 while 循环。
while 循环是一种条件循环,每次循环检测一次循环条件,成立的话就循环一次,然后再检查。
例如这样:
1 |
|
而 do…while 循环则是先循环一次,再判断条件是否成立。
1 |
|
for 循环是一种很常用的循环,它的功能很多,甚至在 C++14 及以后的版本里可以直接遍历一个不定量数组。
它的模板是 for(定义(也可以直接使用已定义的)循环变量=初始值;循环条件;变化规则)
,当然三个语句不一定都要写,但是分号不能省。如前两个样例也可以写成这样:
1 |
|
输出 n 次 “Hello, World!”
本题是自编题。
题目描述
给定一个 n ,输出 n 行 Hello, World!
。
样例输入
1 |
|
样例输出
1 |
|
这道题是在 Hello, World! 的基础上加上了循环。根据它需要循环 n 次,不难想出这道题需要使用 for 循环。
所以代码可以这么写:
1 |
|
课后习题
N 个 A+B
本题是自编题。
题目描述
给定 N,接下来的 N 行每行两个整数 A、B,求 A+B,输出共 N 行。
输入样例
1 |
|
输出样例
1 |
|
1.7 一维数组
如果我们需要输入 10 个整数,并且输出它们的和,只用变量的话,我们就需要像 int a,b,c,d,e,f,g,h,i,j;
这样定义 10 个变量,非常麻烦。那么,如果可以把它们合并成一个长条,就方便多了。于是,C++ 里引进了数组的思想。
我们可以把数组看成这样的一个长条:
我们在定义数组时,只需要在变量的基础上,在后面加速中括号并填上数组大小即可。如:定义一个长度为 10 的整型数组 A,就可以写作 int A[10];
,如需访问一个值,就在中括号里写上下标位置即可。如输出 A 的第 6 个值,可以写作 cout<<A[5]
。它的下标是0~9。
查找特定的值
这道题我们可以使用顺序查找的方式,先看看数组的第0位(下标从零计)是不是目标值,如果是,输出下标。否则查找下一位。
代码如下:
1 |
|
这样类型的题如果 n 太大的话(例如1 <= n <= 10^9),顺序查找就无法在指定时间内完成了(时间复杂度 O(n) ),对于一个有序的数组(单调递不增或单调不降),我们可以使用二分查找的方式解决,在后面会详细讲解。
课后习题
提示:此题可以使用两个循环变量实现,例如 for(int i=1,j=n;i<=n;i++,j--)
提示:此题需要循环嵌套(在一个循环里写循环)
二、基础算法
1.1 模拟算法
模拟是一种最基础的算法,它的策略是按照题目的要求,一步一步的进行模拟各个值的变化,从而获取答案。
Peter 的烟
题目描述
Peter 有 n 根烟,他每吸完一根烟就把烟蒂保存起来,k(k > 1)个烟蒂可以换一个新的烟,那么 Peter 最终能吸到多少根烟呢?
与某些脑筋急转弯不同的是,Peter 并不能从异次元借到烟蒂,抽完后再还回去。
输入格式
每组测试数据一行包括两个整数 n,k (1 < n,k <= 1e8)。
输出格式
对于每组测试数据,输出一行包括一个整数表示最终烟的根数。
输入样例
1 |
|
输出样例
1 |
|
本题我们可以先吸一根烟,随后再检查一下能不能用烟蒂换一些烟,如果可以,就把烟蒂数清零,并且把烟的数量加 1,然后再抽一根烟……以此类推。
此处我们就需要刚才讲的循环了。
代码如下:
1 |
|