机试 C / C++ 注意事项

本文为《算法笔记》的学习记录

概念扫盲~

  • 在线评测系统 (Online Judge, OJ): 一般来说, 在OJ上可以看到题目的题目描述、 输入格式、 输出格式、 样例输入及样例输出。如:PATcodeup
  • 评测结果
    • 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 2311 的初值时,需要在初值后加上 LL。否则会编译错误
  • 单精度浮点数 float 的尾数有 23 位,因此有效精度只有 6~7 位 ( 2 23 = 8388608 2^{23}=8388608 223=8388608)。如果题目的精度要求较高,float就不太合适。因此,原则上浮点数全部用double
  • 小写字母比大写字母的 ASCII 码值大 32
  • 如果想定义常量,宏定义和const都可以,但推荐用const
  • 程序中无穷大的数可以表示为 (1 << 30) - 10x3fffffff
  • 循环 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存放+1acos(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 n1 chars or ‘\n’, whichever comes first, append ‘\0’ and put the whole thing into string. (表示从标准输入最多读入 n − 1 n-1 n1 个字符到字符数组 c 中,如果遇到换行符则读入换行符然后在换行符后面加 ‘\0’)
    • 要注意数组 c 一定要多留两个位置给 ‘\n’ 和 '\0'
  • 几个有用的 vector 成员函数:
    • v.clear()
    • v.resize(n): 扩容,且新增元素默认为 0
  • 字符串与数值之间的转换
    在这里插入图片描述
  • 小技巧: 由于空格在测试时肉眼看不出来, 因此如果提交返回“格式错误“, 可以把程序中的空格改成其他符号(比如#)来输出, 看看是哪里多了空格
  • 原则上不要对 vector 的元素取地址,除非所有的 vector 元素已经填充完毕,这样 vector 的元素不会发生位置移动,地址才不会变,这样才能确保取得的地址的有效性

  • 记得熟悉一下 vector, string, map, set, stack, queue, priority_queue, pair… 的用法

scanf / printf

#include <cstdio>
  • 输入和输出请使用 scanfprintf ! (它们比 cincout 要快得多。并且不要混用它们和 cincout,有时会出问题)

  • 如果要输入"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详细讲解——你真的会用吗?

  • 如果 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

  • scanfprintf 中的 标准输入 / 输出流 换成了字符数组
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 rp
  • sqrt(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(数组元素类型))
  • 只建议使用memset0-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
    • 对于任一变量 acomp(a,a)false
    • 如果 comp(a,b)true,那么 comp(b,a)false
    • 如果 comp(a,b)truecomp(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_rangelower_boundupper_bound 算法返回迭代器, 指向给定元素在序列中的正确插入位置----插入后还能保持有序。如果给定元素比序列中的所有元素都大, 则会返回尾后迭代器
  • 每个算法都提供两个版本: 第一个版本用元素类型的小于运算符来检测元素;第二个版本则使用给定的比较操作。在下列算法中,"x 小于 y" 表示 x<ycomp(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 时, 认为它们相等
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值