本文为《算法笔记》的学习记录
目录
概念扫盲~
- 在线评测系统 (Online Judge, OJ):
一般来说, 在OJ上可以看到题目的题目描述、 输入格式、 输出格式、 样例输入及样例输出。如:PAT、codeup - 评测结果
AC:Accepted (答案正确)
段错误:由数组越界,堆栈溢出(比如,递归调用层数太多)等情况引起
黑盒测试
黑盒测试是指: 系统后台会准备若干组输入数据,然后让提交的程序去运行这些数据,如果输出的结果与正确答案完全相同(字符串意义上的比较),那么就称通过了这道题的黑盒测试,否则会根据错误类型而返回不同的结果
单点测试 (PAT)
对单点测试来说, 系统会判断每组数据的输出结果是否正确。如果输出正确, 那么对该组数据来说就通过了测试, 并获得了这组数据的分值
多点测试 (codeup)
与单点测试相对,多点测试要求程序能一次运行所有数据, 并要求所有输出结果都必须完全正确,才能算作这题通过;而只要有其中一组数据的输出错误,本题就只能得0分
由于要求程序能运行所有数据, 因此必须保证程序有办法反复执行代码的核心部分,这就要用到循环。而题目一般会有3 种输入的格式,需要采用不同的输入方式。下面分别讲述这3 种输入方式
while … EOF
型
如果题目没有给定输入的结束条件, 那么就默认读取到文件末尾
那么如何解决这种输入要求呢?首先需要知道,scanf
函数的返回值为其成功读入的参数的个数。在读取文件时到达文件末尾导致的无法读取现象, 就会产生读入失败,返回-1
, 且C 语言中使用EOF
来代表-1
。因此可以利用scanf
的返回值是否为EOF
来判断输入是否结束:
while (scanf("%d", &n) != EOF) {
...
}
如果想要在黑框里面手动触发
EOF
, 可以按<Ctrl + Z>
组合键
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);
}
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四舍五入可以通过奇偶判断,以回避浮点数运算
- 如果用字符数组的话,可以引用头文件
<cstring>
来使用strlen
等函数 - 如果想读入一行字符串到字符数组,则需要使用
fgets(c, n, stdin)
(表示从标准输入读入n个字符到字符数组c中),要注意fgets
是会读入末尾的换行符然后在换行符后面加’\0’的,同时在使用fgets
之前可能需要用getchar
来读入多余的换行符,否则fgets
会只读入换行符就结束了
scanf
/ printf
- 输入和输出请使用
scanf
和printf
!(#include <cstdio>
) (它们比cin
和cout
要快得多。并且不要混用它们和cin
、cout
,有时会出问题
如果要输入"3 4" 这种用空格隔开的两个数字, 两个%d
之间可以不加空格:
int a, b;
scanf("%d%d", &a, &b);
可以不加空格的原因是,除了%c
外,scanf
对其他格式符(如%d
)的输入是以空白符(即空格、Tab) 为结束判断标志的, 因此除非使用%c
把空格按字符读入, 其他情况都会自动跳过空格。另外, 字符数组使用%s
读入的时候以空格跟换行为读入结束的标志, 如下面的代码所示:
char str[10];
scanf("%s", str);
printf("%s", str);
输入数据:
abed efg
输出结果:
abed
由表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)
:取绝对值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
的执行速度快)
常用的泛型函数
#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
的第三个实参
// 比较函数, 用来按长度排序单词
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
相同,但是是稳定的排序方法