ACM入门之读入输出优化

Posted 辉小歌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ACM入门之读入输出优化相关的知识,希望对你有一定的参考价值。

本文主要是基于oiwiki的做的总结,未来也会做一系列的相关文章。如果想继续看的话可以关注专栏。
做这个专栏的目的是因为自己最近开始系统的再打一下基础。于是想写一个专栏便于喜爱ACM的初学者入门。

为啥要读入、输出优化

其实,读入、输出优化的目的就是防止我们因为读入、输出的问题使我们的程序TLE。于是我们需要读入、输出优化。
来减少我们的运行时间,达到AC题目的目的。

那么对于每道题我们是否需要必须进行读入、输出优化呢?

答:其实,这个其实是根据具体的题目来分析的。如果题目的输入/输出的数据很多(例如:1e5以上)那么保险起见是要加上优化的。
当然其实根据很多高手的习惯他们是不管题目的输入/输出多不多,直接加上优化的。

优化方法一: 关闭同步/解除绑定

scanf printf 的速度 是比 cin cout 的速度快很多的。
这是因为:C++ 为了兼容 C,保证程序在使用了 printf scanfcin cout的时候不发生混乱,将输入/输出流绑到了一起,故时间就慢了很多。
解决办法如下:

  • std::ios::sync_with_stdio(false);
    这个函数是一个“是否兼容 stdio的开关,C++ 为了兼容 C,保证程序在使用了 printfcout的时候
    不发生混乱,将输出流绑到了一起。
    这其实是 C++ 为了兼容而采取的保守措施。我们可以在进行 IO 操作之前将 stdio 解除绑定,但是在这样做之后
    要注意不能同时使用 cin cout 和 printf scanf
  • tie
    tie是将两个stream绑定的函数,空参数的话返回当前的输出流指针。
    在默认的情况下 s t d : : c i n 绑定的是 s t d : : c o u t ,每次执行 < < 操作符的时候都要调用 f l u s h ( ) ,这样会增加IO 负担。可以通过 s t d : : c i n . t i e ( 0 ) (0 表示 NULL)来解除 s t d : : c i ns t d : : c o u t 的绑定,进一步加快执行效率。

那么总的优化代码如下:

std::ios::sync_with_stdio(false);
std::cin.tie(0);
//如果编译开启了 C++11 或更高版本,建议使用 std::cin.tie(nullptr);

优化方法二: 快读、快写

scanfprintf 依然有优化的空间。注意: 这里的快读、快写只是针对整数,浮点数的话自行编写。
原理
众所周知, getchar是用来读入 1 byte 的数据并将其转换为 char 类型的函数,且速度很快,故可以用“读入字符——转换为整型”来代替缓慢的读入每个整数由两部分组成——符号和数字整数的’+’ 通常是省略的,且不会对后面数字所代表的值产生影响,而’-’ 不可省略,因此要进行判定10 进制整数中是不含空格或除 0~9 和正负号外的其他字符的,因此在读入不应存在于整数中的字符(通常为空格)时,就可以判定已经读入结束C 和 C++ 语言分别在 ctype.hcctype 头文件中,提供了函数isdigit , 这个函数会检查传入的参数是否为十进制数字字符,是则返回 true,否则返回 false。对应的,在下面的代码中,可以使用 isdigit(ch) 代替 ch > = ' 0 '& & ch<= ' 9 '
而可以使用 ! isdigit(ch)代替 c h < ' 0 ' | | c h > ' 9 '
那么模板如下:

template <typename T>
inline T read() 
 
 	//声明 template 类, 要求提供输入的类型 T, 并以此类型定义内联函数 read()
	T sum = 0, fl = 1; // 将 sum,fl 和 ch 以输入的类型定义
	int ch = getchar();
	for (; !isdigit(ch); ch = getchar())
	if (ch == '-') fl = -1;
	for (; isdigit(ch); ch = getchar()) sum = sum * 10 + ch - '0';
	return sum * fl;

template <typename T>
inline void write(T x) 

    static int sta[35];
    int top = 0;
    do 
        sta[top++] = x % 10, x /= 10;
     while (x);
    while (top) putchar(sta[--top] + 48); // 48 是 '0'

使用方法如下:

a = read<int>();
b = read<long long>();
c = read<__int128>();
write(a);

各种情况下的时间对比



通过上图你基本会有一个大致的认知了。最后还可以加上火车头来进一步的优化。加上火车头以后,我们的时间又会少了点。
火车头代码如下:

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
不推荐使用火车头,正式的比赛好像都不让用。

总的模板

通过上文,可以得到以下几种模板。
模板一: 快读、快写

  • 好处: 我们可以cin cout printf scanf 混着用
  • 坏处:模板有点长
template <typename T>
inline T read() 
 
 	//声明 template 类, 要求提供输入的类型 T, 并以此类型定义内联函数 read()
	T sum = 0, fl = 1; // 将 sum,fl 和 ch 以输入的类型定义
	int ch = getchar();
	for (; !isdigit(ch); ch = getchar())
	if (ch == '-') fl = -1;
	for (; isdigit(ch); ch = getchar()) sum = sum * 10 + ch - '0';
	return sum * fl;

template <typename T>
inline void write(T x) 

    static int sta[35];
    int top = 0;
    do 
        sta[top++] = x % 10, x /= 10;
     while (x);
    while (top) putchar(sta[--top] + 48); // 48 是 '0'

模板二: 关闭缓冲区和同步

  • 好处: 优化的代码很短,只需两行
  • 坏处: 优化后只可以cin cout
std::ios::sync_with_stdio(false);
std::cin.tie(0);

个人推荐:
我一般喜欢不用模板,一般将输入数据多的,输出数据多的语句用scanf printf即可。
如果是打线上的一些比赛(例如 cf),会用第一种方法因为当要一些格式化的输出时我喜欢用printf

以上是关于ACM入门之读入输出优化的主要内容,如果未能解决你的问题,请参考以下文章

QT5入门之12 - QDebug输出调试信息

读入优化 && 输出优化

[读入输出优化]4.15正确的打开方式

读入输出优化

读入优化与输出优化

常用读入输出优化