快速读入
快速读入
我们平时写代码基本都使用 cin 进行读入,但众所周知,cin 的读入速度很慢,无法胜任输入数据量很大的题(如 洛谷 P5745 【深基附B例】区间最大和 ),所以算法竞赛中常用速度更快的读入方法。接下来就要介绍到常用的快速读入算法。
0. 前置知识
下面比较几种常用输入函数的速度。
cin < scanf < getchar < getchar_unlocked(Linux)/_getchar_nolock(Windows) < fread
1. cin 优化(关闭同步 和 解除绑定)
cin 读入速度慢的两个主要原因是与 stdio 同步和与 cout 绑定,所以 cin 优化的瓶颈就在于解除 stdio 同步和 cout 绑定,其弊端有:
- 不能混用
cin和scanf,也不能混用cout和printf; - 如果要在输入之前输出,则输出后需要
cout.flush();或cout << flush;。
实现方法
只需要在主函数最前面加上这两句代码即可:
ios::sync_with_stdio(0);
cin.tie(0);
这样 cin 的速度就接近 scanf 了。
2. getchar(最常用)
getchar 函数是用来读取 1 字节的输入数据并将其转化为 char 类型(但返回值为 int ),且速度比 scanf 快。所以我们可以使用 getchar 来实现整数的读入。
基本思路
逐位输入这个整数,用变量 x 来存储。如果第一位是 - ,则将变量 k 标记为 \(-1\),否则标记为 \(1\)。如果输入的是数字,则将变量 x 乘 \(10\) 之后再加上输入字符 \(-\) ' \(0\) ' ,直到输入空格或回车为止。
如何判断输入字符是不是数字?有一下两种方法:
- 如果
ch >= '0' && ch <= '9',则输入的是数字;如果ch < '0' || ch > '9',则输入的不是数字。 - 还可以使用
isdigit来判断:如果isdigit(ch),则输入的是数字,反之则不是。这种方法的速度其实比第 1 种还要快。(需要#include <cctype>)
代码实现
#include <cstdio>
#include <cctype>
inline int read() { // 使用内联函数提升效率
int x = 0, k = 1, ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') k = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * k;
}
int main() {
int a = read(); // 使用方法
}
3. getchar_unlocked (Linux) / _getchar_nolock (Windows)
普通的 getchar 函数带有线程锁,但算法竞赛中用不到,所以可以关闭线程锁以提高效率。
具体实现
将上面代码中的所有 getchar 替换为 getchar_unlocked (Linux) / _getchar_nolock (Windows) 即可。
(p.s. NOI 系列竞赛评测机和洛谷评测机均为 Linux 系统,所以打 NOI 系列竞赛或在洛谷上提交评测都要用 getchar_unlocked )
#include <cstdio>
#include <cctype>
inline int read() { // 使用内联函数提升效率
int x = 0, k = 1, ch = getchar_unlocked();
while (!isdigit(ch)) {
if (ch == '-') k = -1;
ch = getchar_unlocked();
}
while (isdigit(ch)) {
x = x * 10 + ch - '0';
ch = getchar_unlocked();
}
return x * k;
}
int main() {
int a = read(); // 使用方法
}
4. fread (不常用)
函数原型:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
fread 的作用是从 stream 中读入 nmemb 个 size 字节大小的数据,并存储到 ptr 中,并返回读入的字节数。
例如:fread(buf, 1, 1 << 20, stdin); 可以从标准输入流中读入 \(2^{20}\) 个 \(1\) 字节的字符,并存储到 buf 数组中。
根据 fread 函数,我们可以重新写一个 getchar 函数。
使用 fread 实现 getchar
#include <cstdio>
char buf[1 << 20], *p1, *p2; /* buf 用来存储输入数据,p1 是 buf 的首指针,p2 是 buf 的尾指针 */
char gc() {
if (p1 == p2) { // 如果 buf 中的数据全部读入完毕,则重新读入
p1 = buf;
p2 = buf + fread(buf, 1, 1 << 20, stdin); // 再读入 2^20 个字符
if (p1 == p2) return EOF; /* 如果 p1==p2,则说明没有读入成功,即读入到文件末尾,返回 EOF */
}
return *p1++; // 返回读入的字符,并指向下一个字符
}
事实上,多次调用函数很浪费时间,甚至没有 getchar 快,所以实际使用时会写成宏定义形式。
#include <cstdio>
#include <cctype>
char buf[1 << 20], *p1, *p2;
#define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin)), *p1++)
inline int read() { // 使用内联函数提升效率
int x = 0, k = 1, ch = gc();
while (!isdigit(ch)) {
if (ch == '-') k = -1;
ch = gc();
}
while (isdigit(ch)) {
x = x * 10 + ch - '0';
ch = gc();
}
return x * k;
}
int main() {
int a = read(); // 使用方法
}
注意
- 使用
fread需要在输入完毕之后手动打 EOF (Linux 为Ctrl+D,Windows 为Ctrl+Z) 。 - 使用此快读就不要使用
cin、scanf或getchar,否则会因为缓存问题出错。
至此,常规的快速读入方法都已经介绍完毕。更多非常规的快读方法(如:mmap 和打表优化)在这里不再赘述。本文参考 洛谷专栏 【输入 / 输出优化】浅谈 C++ IO优化(快读快写) 撰写,鸣谢此专栏作者 @Famishin。

浙公网安备 33010602011771号