本文为《算法笔记》的学习记录
目录
概念扫盲~
- 在线评测系统 (Online Judge, OJ): 一般来说, 在OJ上可以看到题目的题目描述、 输入格式、 输出格式、 样例输入及样例输出。如:PAT、codeup
- 评测结果
- AC:Accepted (答案正确)
- 段错误:由数组越界,堆栈溢出(比如,递归调用层数太多)等情况引起
黑盒测试
- 黑盒测试是指: 系统后台会准备若干组输入数据,然后让提交的程序去运行这些数据,如果输出的结果与正确答案完全相同(字符串意义上的比较),那么就称通过了这道题的黑盒测试,否则会根据错误类型而返回不同的结果
单点测试 (PAT)
- 对单点测试来说, 系统会判断每组数据的输出结果是否正确。如果输出正确, 那么对该组数据来说就通过了测试, 并获得了这组数据的分值
多点测试 (codeup)
- 与单点测试相对,多点测试要求程序能一次运行所有数据, 并要求所有输出结果都必须完全正确,才能算作这题通过;而只要有其中一组数据的输出错误,本题就只能得0分
- 由于要求程序能运行所有数据, 因此必须保证程序有办法反复执行代码的核心部分,这就要用到循环。而题目一般会有 3 种输入的格式,需要采用不同的输入方式
- 同时需要指出,在多点测试中, 每一次循环都要重置一下变量和数组, 否则在下一组数据来临的时候变量和数组的状态就不是初始状态了
(1) while … EOF
型
- 如果题目没有给定输入的结束条件, 那么就默认读取到文件末尾
- 那么如何解决这种输入要求呢?首先需要知道,
scanf
函数的返回值为其成功读入的参数的个数。在读取文件时到达文件末尾导致的无法读取现象, 就会产生读入失败,返回-1
, 且 C 语言中使用EOF
来代表-1
。因此可以利用scanf
的返回值是否为EOF
来判断输入是否结束:
while (scanf("%d", &n) != EOF) {
...
}
如果想要在黑框里面手动触发
EOF
, 可以按<Ctrl + Z>
组合键
(2) while … break
型
- 题目要求当输入的数据满足某个条件时停止输入
while(scanf("%d%d", &a, &b) != EOF) {
if (a == 0 && b == 0) break;
printf("%d\n", a + b);
}
- 而另一种更简洁的写法是,把退出条件的判断放到
while
语句中,令其与scanf
用逗号隔开
while (scanf ("%d%d", &a, &b), a || b) {
printf("%d\n", a + b);
}
(3) while(T--)
型
- 在这种类型中,题目会给出测试数据的组数。由于给定了测试数据的组数,因此需要用一个变量
T
来存储,并在程序开始时读入。在读入T
后,下面就可以进行T
次循环
C / C++ 注意事项
s.c_str()
:返回string
转化的字符数组,末尾自动加'\0'
- 在给
long long
类型变量赋大于 2 31 − 1 2^{31} − 1 231−1 的初值时,需要在初值后加上LL
。否则会编译错误 - 单精度浮点数
float
的尾数有 23 位,因此有效精度只有 6~7 位 ( 2 23 = 8388608 2^{23}=8388608 223=8388608)。如果题目的精度要求较高,float
就不太合适。因此,原则上浮点数全部用double
- 小写字母比大写字母的 ASCII 码值大 32
- 如果想定义常量,宏定义和
const
都可以,但推荐用const
- 程序中无穷大的数可以表示为
(1 << 30) - 1
或0x3fffffff
- 循环
n
n
n 次的简洁写法:
i = n; while(i--){}
- 浮点数比较时,使用
const double eps = 1e-8
可以将比较操作宏定义为
#define EQU(a,b) ((fabs((a)-(b)))<(eps))
#define MORE(a,b) (((a)-(b))>(eps))
#define LESS(a,b) (((a)-(b))<(-eps))
#define MOREEQU(a,b) (((a)-(b))>(-eps))
#define LESSEQU(a,b) (((a)-(b))<(eps))
const double PI = acos(-1.0);
- 由于精度问题,在经过大量运算后,可能一个变量中存储的 0 是个很小的负数,这时如果对其开根号
sqrt
, 就会因不在定义域内而出错。同样的问题还出现在asin(x)
当x
存放+1
、acos(x)
当x
存放-1
时。这种情况需要用eps
使变量保证在定义域内 - 对时间复杂度为 O ( n 2 ) O(n^2) O(n2) 的算法来说, 当 n n n 的规模为 1000 1000 1000 时, 其运算次数大概为 1 0 6 10^6 106 级别;对一般的OJ系统来说, 一秒能承受的运算次数大概是 1 0 7 1 0 8 10^7 ~ 10^8 107 108 , 因此 O ( n 2 ) O(n^2) O(n2) 的算法当 n n n 的规模为1000时是可以承受的, 而当 n n n 的规模为100000时则是不可承受的
- 在一般的应用中, 一般来说空间都是足够使用的(只要不开好几个
1
0
7
10^7
107 以上的数组即可,例如
int A[10000][10000]
的定义就是不合适的 - 奇偶判断: 整数对 2 取余, 或者进行位与运算
b & 1
,这样速度更快 - 如果用字符数组的话,可以引用头文件
<cstring>
来使用strlen
等函数 - 如果想读入一行字符串到字符数组,则需要使用
fgets(c, n, stdin)
- get a string, up to
n
−
1
n-1
n−1 chars or ‘
\n
’, whichever comes first, append ‘\0
’ and put the whole thing into string. (表示从标准输入最多读入 n − 1 n-1 n−1 个字符到字符数组c
中,如果遇到换行符则读入换行符然后在换行符后面加 ‘\0
’) - 要注意数组
c
一定要多留两个位置给 ‘\n
’ 和 '\0
'
- get a string, up to
n
−
1
n-1
n−1 chars or ‘
- 几个有用的
vector
成员函数:v.clear()
v.resize(n)
: 扩容,且新增元素默认为 0
- 字符串与数值之间的转换
- 小技巧: 由于空格在测试时肉眼看不出来, 因此如果提交返回“格式错误“, 可以把程序中的空格改成其他符号(比如
#
)来输出, 看看是哪里多了空格 - 原则上不要对
vector
的元素取地址,除非所有的vector
元素已经填充完毕,这样vector
的元素不会发生位置移动,地址才不会变,这样才能确保取得的地址的有效性
- 记得熟悉一下
vector
,string
,map
,set
,stack
,queue
,priority_queue
,pair
… 的用法
scanf
/ printf
#include <cstdio>
- 输入和输出请使用
scanf
和printf
! (它们比cin
和cout
要快得多。并且不要混用它们和cin
、cout
,有时会出问题)
- 如果要输入"3 4" 这种用空格隔开的两个数字, 两个
%d
之间可以不加空格。可以不加空格的原因是,除了%c
外,scanf
对其他格式符(如%d
)的输入是以空白符(即空格、Tab) 为结束判断标志的, 因此除非使用%c
把空格按字符读入, 其他情况都会自动跳过空格:- 如果想在
%c
之前读入一个字符 (如换行符),可以用%*c
或先用getchar()
读出来
- 如果想在
int a, b;
scanf("%d%d", &a, &b);
- 另外, 字符数组使用
%s
读入的时候以空格跟换行为读入结束的标志:
char str[10];
scanf("%s", str);
printf("%s", str);
输入数据:
abed efg
输出结果:
abed
- 如果
scanf
的格式控制符之间有空格、回车符、制表符,则它们的作用是吸收空格、制表符和回车符 (不论格式控制符之间有没有空格、制表符、回车符,有多少空格、制表符、回车符,都是一样的效果);例如:
int a,b;
scanf("%d %d",&a,&b);
scanf("%d %d",&a,&b);
scanf("%d\n%d",&a,&b);
scanf("%d\t%d",&a,&b);
- 特别的:最后一个格式符后有空格、制表符、回车符,有时候会使得数据的读取发生错误;例如下面的情况,如果输入
1\n
是无法读入的,因为最后的回车会被吸收掉:
scanf("%d\n",&a);
scanf("%d\t",&a);
scanf("%d%d ",&a,&b);
- 由表2-6 可见,
double
的输出格式变成了%f
, 而在scanf
中却是%lf
。在有些系统中如果把输出格式写成%lf
倒也不会出错,不过尽量还是按标准来
- 如果想要输出’
%
’’\
’, 则需要在前面再加一个%
或\
, 例如:
printf("%%");
printf("\\");
%0md
:使不足m
位的int
型变量以m
位进行右对齐输出,其中高位用0
补齐;如果变量本身超过m
位, 则保持原样
getchar
/putchar
getchar
可以读入换行符
sscanf
/ sprintf
- 将
scanf
和printf
中的 标准输入 / 输出流 换成了字符数组
sscanf(str, "%d", &n);
sprintf(str, "%d", n);
int n;
char str[100] = "123";
sscanf(str, "%d", &n);
printf("%d\n", n);
输出结果:
123
int n = 233;
char str[100];
sprintf(str, "%d", n);
printf("%s\n", str);
输出结果:
233
sscanf
还支持正则表达式, 如果配合正则表达式来进行字符串的处理, 那么很多字符串的题目都将迎刃而解
常用 math 函数
#include <cmath>
fabs(double x)
:取绝对值 (整型取绝对值可以用abs
)floor(double x)
和ceil(double x)
:分别用于向下和向上取整pow(double r, double p)
:返回 r p r^p rpsqrt(double x)
log(double x)
:返回 l o g e x log_ex logex ( l o g a b = log e b l o g e a log_ab=\frac{\log_eb}{log_ea} logab=logealogeb)sin(double x)
、cos(double x)
和tan(double x)
: 参数要求是弧度制asin(double x)
、acos(double x)
和atan(double x)
: 这三个函数分别返回double
型变量的反正弦值、反余弦值和反正切值 (pi=acos(-1.0)
)round(double x)
:四舍五入
数组初始化
memset
#include <cstring>
memset(数组名, 值, sizeof(数组名))
memset(数组名, 值, 数组大小 * sizeof(数组元素类型))
- 只建议使用
memset
赋0
或-1
。这是因为memset
使用的是按字节赋值, 即对每个字节赋同样的值, 这样组成int
型的4个字节就会被赋成相同的值。而0的二进制补码为全0, -1的二进制补码为全1, 可以直接将数组内所有元素赋值为0或-1
如果要对数组赋其他数字(例如1), 那么请使用
fill
函数(但memset
的执行速度快)
STL 容器
vector
v.front()
:返回当前vector
容器中起始元素的引用v.back()
:返回当前vector
容器中末尾元素的引用v.pop_back()
:高效销毁vector
容器的末尾元素v.clear()
: 情况vector
内所有元素。时间复杂度为 O ( n ) O(n) O(n)v.insert(it, x)
: 向v
的任意迭代器it
处插入一个元素x
,原来在这个位置及之后的元素都相后移。时间复杂度为 O ( n ) O(n) O(n)v.erase(it)
: 删除迭代器it
处的元素;v.erase(first, end)
: 删除[first, last)
内的所有元素。时间复杂度均为 O ( n ) O(n) O(n)
常用的泛型算法
#include <algorithm>
sort
sort
就是用来排序的函数, 默认使用<
进行升序排序。它根据具体情形使用不同的排序方法, 效率较高
vector<int> v{3, 2, 1};
sort(v.begin(), v.end()); // 使用迭代器
int a[] = {3, 2, 1};
sort(begin(a), end(a)); // 需要 #include <iterator>
// 或者
sort(a, a + sizeof(a) / sizeof(int));
- 也可以自定义一个二元谓词作为比较函数,作为
sort
的第三个实参。但一定要注意,这个比较函数必须满足严格弱序的条件,否则会报错:invalid comparator- 对于任一变量
a
,comp(a,a)
为false
- 如果
comp(a,b)
为true
,那么comp(b,a)
为false
- 如果
comp(a,b)
为true
且comp(b,c)
为true
,则comp(a,c)
为true
- 对于任一变量
// 比较函数, 用来按长度排序单词
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
// 按长度由短至长排序 words
sort(words.begin(), words.end(), isShorter);
// 比较函数也可以用lambda 来更方便的实现
sort(words.begin(), words.end(),
[] (const string &a, const string &b)
{ return a.size() < b.size(); });
- 如果想要降序排序,最简单的方法是使用反向迭代器:
sort(vec.rbegin(), vec.rend());
stable_sort
- 用法与
sort
相同,但是是稳定的排序方法
max
, min
, minmax
min(val1, val2)
min(val1, val2, comp)
min(init_list)
min(init_list, comp)
max(val1, val2)
max(val1, val2, comp)
max(init_list)
max(init_list, comp)
minmax(val1, val2)
minmax(val1, val2, comp)
minmax(init_list)
minmax(init_list, comp)
min_element(beg, end)
min_element(beg, end, comp)
max_element(beg, end)
max_element(beg, end, comp)
minmax_element(beg, end)
minmax_element(beg, end, comp)
swap
int a = 1, b = 2;
swap(a, b); // 交换 a, b 的值
reverse
reverse(beg, end)
reverse_copy(beg, end, dest)
- 翻转序列中的元素。
reverse
返回void
,reverse_copy
返回一个迭代器, 指向拷贝到目的序列的元素的尾后位置
next_permutation
, prev_permutation
next_permutation(beg, end)
next_permutation(beg, end, comp)
- 如果序列已经是最后一个排列, 则
next_permutation
将序列重排为最小的排列, 并返回false
。否则, 它将输入序列转换为字典序中下一个排列, 并返回true
- 第一个版本使用元素的
<
运算符比较元素, 第二个版本使用给定的比较操作
int a[3] = { 1, 2, 3 };
do{
printf("%d%d%d\n", a[0], a[1], a[2]);
}while(next_permutation(a, a + 3));
output:
123
132
213
231
312
321
prev_permutation(beg, end)
prev_permutation(beg, end, comp)
- 类似
next_permutation
, 但将序列转换为前一个排列。如果序列已经是最小的排列,则将其重排为最大的排列, 并返回false
fill
fill(beg, end, val)
fill_n(dest, cnt, val)
- 给输入序列中每个元素赋予一个新值。
fill
将值val
赋予元素 _n
版本假定写入指定个元素是安全的,并返回一个迭代器,指向写入到输出序列的最后一个元素之后的位置
// 将容器的一个子序列设置为 10
fill(vec.begin(), vec.begin() + vec.size() / 2, 10);
vector<int> vec;
// 修改 10 个不存在的元素;这条语句的结果是未定义的
fill_n(vec.begin(), 10, 0);
//正确: back_inserter 创建一个插入迭代器, 可用来向 vec 添加元素
fill_n(back_inserter(vec), 10, 0); //添加 10 个元素到 vec
二分搜索算法
- 这些算法要求序列中的元素已经是有序的
equal_range
、lower_bound
和upper_bound
算法返回迭代器, 指向给定元素在序列中的正确插入位置----插入后还能保持有序。如果给定元素比序列中的所有元素都大, 则会返回尾后迭代器- 每个算法都提供两个版本: 第一个版本用元素类型的小于运算符来检测元素;第二个版本则使用给定的比较操作。在下列算法中,"
x
小于y
" 表示x<y
或comp(x,y)
成功
lower_bound(beg, end, val)
lower_bound(beg, end, val, comp)
- 返回一个迭代器,表示第一个大于或等于
val
的元素,如果不存在这样的元素,则返回end
upper_bound(beg, end, val)
upper_bound(beg, end, val, comp)
- 返回一个迭代器, 表示第一个大于
val
的元素, 如果不存在这样的元素, 则返回end
equal_range(beg, end, val)
equal_range(beg, end, val, comp)
- 返回一个
pair
, 其first
成员是lower_bound
返回的迭代器,second
成员是upper_bound
返回的迭代器
binary_search(beg, end, val)
binary_search(beg, end, val, comp)
- 返回一个
bool
值, 指出序列中是否包含等于val
的元素。对于两个值 x x x 和 y y y,当 x x x 不小于 y y y 且 y y y 也不小于 x x x 时, 认为它们相等