快读快写 - 学习笔记
在OI中,经常有输入输出量巨大的题,这一类题一般需要非常快速的输入输出方式,于是便有了快读快写
下面是模板(原理无需理解,用的时候直接复制上就行):
注意:程序末尾一定要刷新缓存区!
#include <cstdio>
#include <cctype>
using namespace std;
int precision=-1;
char buf[100000],*p1=buf,*p2=buf;
#define nextChar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
inline int scan_int(){
int x=0,f=1;
char ch=nextChar();
while(ch<48||ch>57){
if(ch=='-')f=-1;
ch=nextChar();
}
while(ch>=48&&ch<=57){
x=(x<<3)+(x<<1)+(ch^48);
ch=nextChar();
}
return x*f;
}
inline double power(double a,int k){
double res=1;
while(k){
if(k&1) res*=a;
a=a*a;
k>>=1;
}
return res;
}
inline double scan_double(){
double x=0,y=0;
int f=1,mine=0;
char ch=nextChar();
while(ch<48||ch>57){
if(ch=='-')f=-1;
ch=nextChar();
}
while(ch>=48&&ch<=57){
x=x*10+(ch^48);
ch=nextChar();
}
if(ch=='.'){
ch=nextChar();
while(ch>=48&&ch<=57){
y=y*10+(ch^48);
mine++;
ch=nextChar();
}
}
precision=mine;
return f*(x+y/power(10,mine));
}
inline bool scan_bool(){
int x=scan_int();
return x!=0;
}
inline char scan_char(){
char ch=nextChar();
while(isspace(ch)) ch=nextChar();
return ch;
}
const int BUF_SIZE=1<<20;
char output_buffer[BUF_SIZE],*output_ptr=output_buffer;
inline void flush_output(){
fwrite(output_buffer,1,output_ptr-output_buffer,stdout);
output_ptr=output_buffer;
}
inline void print_int(int x){
if(x<0){
*output_ptr++='-';
x=-x;
}
char temp[20];
int len=0;
do{
temp[len++]='0'+(x%10);
x/=10;
}while(x);
while(len--) *output_ptr++=temp[len];
if(output_ptr-output_buffer>=BUF_SIZE-20) flush_output();
}
inline void print_double(double x){
char temp[30];
int len;
if(precision<0) len=snprintf(temp,sizeof(temp),"%.f",x);
else len=snprintf(temp,sizeof(temp),"%.*f",precision,x);
for(int i=0;i<len;i++) *output_ptr++=temp[i];
if(output_ptr-output_buffer>=BUF_SIZE-20) flush_output();
}
inline void print_bool(bool x){
const char *str=x?"true":"false";
while(*str) *output_ptr++=*str++;
if(output_ptr-output_buffer>=BUF_SIZE-20) flush_output();
}
inline void print_char(char x){
*output_ptr++=x;
if(output_ptr-output_buffer>=BUF_SIZE-20) flush_output();
}
int main(){
int a=scan_int();
double b=scan_double();
bool c=scan_bool();
char d=scan_char();
print_int(a);
print_char('\n');
print_double(b);
print_char('\n');
print_bool(c);
print_char('\n');
print_char(d);
print_char('\n');
flush_output();
return 0;
}
例题1:洛谷 - P10815 【模板】快速读入
题目链接
实际难度:\(\color{F39C11}{{普及-}}\)
考察知识点
无
思路分析
模板题
复杂度分析
时间复杂度
\[\begin{aligned}
T(n)&=\underbrace{O(n)}_{输入}+\underbrace{O(1)}_{计算}+\underbrace{O(1)}_{输出} \\
&=O(1)
\end{aligned}
\]
空间复杂度
- 主要存储:\(O(1)\)
- 临时变量:\(O(1)\)
- 总空间:\(O(1)\)
C++代码
// Problem: P10815 【模板】快速读入
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P10815
// Memory Limit: 2 MB
// Time Limit: 2500 ms
//
// Powered by CP Editor (https://cpeditor.org)
// 包含标准输入输出库,用于fread、fwrite等底层IO函数
#include <cstdio>
// 包含字符分类函数库,用于isspace(判断空白字符)等函数
#include <cctype>
using namespace std;
// 全局变量:记录浮点数的小数位数(精度),初始为-1表示未指定精度
int precision = -1;
// 输入缓冲区:大小100000字节,用于批量读取输入数据(减少IO次数,提高速度)
char buf[100000];
// 缓冲区指针:p1指向当前读取位置,p2指向缓冲区末尾(已读取数据的边界)
char *p1 = buf, *p2 = buf;
// 宏定义nextChar:从输入缓冲区读取下一个字符,缓冲区空则用fread填充
// 逻辑:若p1==p2(缓冲区已读完),则用fread从stdin读100000字节到buf,更新p1和p2
// 若仍无数据(p1==p2,说明到达EOF),返回EOF;否则返回p1指向的字符并让p1后移
#define nextChar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
// 快速读取整数函数:处理正负整数,比scanf更快(减少格式化解析开销)
inline int scan_int() {
int x = 0; // 存储读取的整数结果,初始为0
int f = 1; // 符号标志:1表示正数,-1表示负数,初始为正
char ch = nextChar(); // 读取第一个字符
// 跳过非数字字符(直到遇到数字或负号)
while (ch < 48 || ch > 57) { // 48是'0'的ASCII,57是'9'的ASCII,此条件判断非数字
if (ch == '-') f = -1; // 若遇到负号,将符号标志设为-1
ch = nextChar(); // 继续读取下一个字符
}
// 读取数字部分,计算整数结果
while (ch >= 48 && ch <= 57) { // 仅处理数字字符
// x = x*10 + (ch-'0'):位运算优化(x<<3是x*8,x<<1是x*2,总和x*10)
// ch^48等价于ch-'0'(因'0'ASCII=48,异或后低4位为数字值)
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = nextChar(); // 继续读取下一个字符
}
return x * f; // 返回带符号的整数结果
}
// 快速幂函数:计算a的k次幂(用于浮点数小数部分的分母计算,a=10,k=小数位数)
inline double power(double a, int k) {
double res = 1; // 结果初始为1(乘法单位元)
while (k) { // 当指数k不为0时循环
if (k & 1) // 若k为奇数(二进制最低位为1),将当前a乘到结果中
res *= a;
a = a * a; // a自乘,指数翻倍(对应二进制右移一位)
k >>= 1; // k右移一位(等价于k=k/2,整数除法)
}
return res; // 返回a^k的结果
}
// 快速读取浮点数函数:处理整数部分、小数部分,支持正负,记录精度
inline double scan_double() {
double x = 0; // 存储浮点数的整数部分,初始为0
double y = 0; // 存储浮点数的小数部分,初始为0
int f = 1; // 符号标志:1正-1负,初始为正
int mine = 0; // 记录小数位数,初始为0
char ch = nextChar(); // 读取第一个字符
// 跳过非数字字符(直到遇到数字、负号或小数点)
while (ch < 48 || ch > 57) {
if (ch == '-') f = -1; // 遇到负号,设置符号为负
ch = nextChar(); // 继续读下一个字符
}
// 读取整数部分
while (ch >= 48 && ch <= 57) {
x = x * 10 + (ch ^ 48); // 整数部分累加(x*10 + 当前数字)
ch = nextChar(); // 继续读下一个字符
}
// 若遇到小数点,处理小数部分
if (ch == '.') {
ch = nextChar(); // 跳过小数点,读取小数部分第一个字符
// 读取小数部分,累加至y(y初始为0,每次乘10加当前数字)
while (ch >= 48 && ch <= 57) {
y = y * 10 + (ch ^ 48);
mine++; // 小数位数+1
ch = nextChar(); // 继续读下一个字符
}
}
precision = mine; // 记录当前浮点数的小数位数(供后续输出使用)
// 计算最终浮点数:符号 * (整数部分 + 小数部分/10^小数位数)
return f * (x + y / power(10, mine));
}
// 快速读取布尔值函数:读取整数,非0为true,0为false
inline bool scan_bool() {
int x = scan_int(); // 调用scan_int读取整数
return x != 0; // 整数非0返回true,否则返回false
}
// 快速读取字符函数:跳过空白字符(空格、换行、制表符等),读取第一个有效字符
inline char scan_char() {
char ch = nextChar(); // 读取第一个字符
// isspace(ch)判断是否为空白字符(空格、\n、\t等),跳过所有空白
while (isspace(ch))
ch = nextChar();
return ch; // 返回第一个非空白字符
}
// 输出缓冲区配置:BUF_SIZE为2^20字节(约1MB),用于批量写入输出数据
const int BUF_SIZE = 1 << 20;
char output_buffer[BUF_SIZE]; // 输出缓冲区数组
char *output_ptr = output_buffer; // 输出缓冲区指针,指向当前写入位置
// 刷新输出缓冲区函数:将缓冲区中已写入的数据写入stdout,重置指针
inline void flush_output() {
// fwrite:从output_buffer写数据到stdout,每次写1字节,共写(output_ptr - output_buffer)字节
fwrite(output_buffer, 1, output_ptr - output_buffer, stdout);
output_ptr = output_buffer; // 指针重置到缓冲区起始位置,准备下次写入
}
// 快速输出整数函数:处理正负整数,写入输出缓冲区(比printf快)
inline void print_int(int x) {
// 若x为负数,先写入负号'-',并将x转为正数
if (x < 0) {
*output_ptr++ = '-'; // 缓冲区指针后移,指向下次写入位置
x = -x;
}
char temp[20]; // 临时数组:存储数字的逆序(因取模得到的是个位、十位...)
int len = 0; // 记录数字的位数,初始为0
// 循环取模获取数字的每一位(从个位开始),存入temp
do {
temp[len++] = '0' + (x % 10); // x%10得到个位,转为字符
x /= 10; // x除以10,去掉个位
} while (x); // 直到x为0(do-while确保x=0时也会存入一个'0')
// 倒序输出temp中的字符(从高位到低位),写入缓冲区
while (len--)
*output_ptr++ = temp[len];
// 检查缓冲区是否即将满(预留20字节防止溢出),满则刷新
if (output_ptr - output_buffer >= BUF_SIZE - 20)
flush_output();
}
// 快速输出浮点数函数:按precision记录的精度格式化,写入输出缓冲区
inline void print_double(double x) {
char temp[30]; // 临时数组:存储格式化后的浮点数字符串
int len; // 记录格式化后字符串的长度
// 按精度格式化浮点数:
// precision<0时,用"%.f"(无小数部分,四舍五入到整数)
// 否则用"%.*f"(*表示精度由precision指定,保留precision位小数)
if (precision < 0)
len = snprintf(temp, sizeof(temp), "%.f", x);
else
len = snprintf(temp, sizeof(temp), "%.*f", precision, x);
// 将格式化后的字符串写入输出缓冲区
for (int i = 0; i < len; i++)
*output_ptr++ = temp[i];
// 检查缓冲区是否即将满,满则刷新
if (output_ptr - output_buffer >= BUF_SIZE - 20)
flush_output();
}
// 快速输出布尔值函数:输出"true"或"false"字符串到缓冲区
inline void print_bool(bool x) {
// 若x为true,取"true"字符串;否则取"false"字符串
const char *str = x ? "true" : "false";
// 遍历字符串,逐个字符写入缓冲区
while (*str) {
*output_ptr++ = *str++; // 将当前字符写入输出缓冲区,指针后移
}
// 检查缓冲区是否即将满(预留20字节防溢出),满则刷新
if (output_ptr - output_buffer >= BUF_SIZE - 20)
flush_output();
}
// 快速输出单个字符函数:将字符写入输出缓冲区,按需刷新
inline void print_char(char x) {
*output_ptr++ = x; // 直接将字符x写入缓冲区,缓冲区指针后移
// 检查缓冲区是否即将满(预留20字节防溢出),满则刷新
if (output_ptr - output_buffer >= BUF_SIZE - 20)
flush_output();
}
// 主函数:示例用法——读取n个整数,计算它们的和并输出
int main() {
// 1. 读取整数n:表示后续要读取的整数个数
int n = scan_int();
// 2. 定义sum变量:用于累加n个整数的和,初始化为0
int sum = 0;
// 3. 循环n次:每次读取一个整数,累加到sum中
for (int i = 1; i <= n; i++) {
sum += scan_int(); // 调用快速读入函数获取整数,加入总和
}
// 4. 输出结果:
print_int(sum); // 调用快速输出函数,将累加和写入输出缓冲区
print_char('\n'); // 输出换行符,确保结果格式正确(单独占一行)
flush_output(); // 强制刷新输出缓冲区:确保所有数据都写入stdout(避免缓冲区残留)
return 0; // 程序正常结束
}