必须强调下,以下的任意一种优化,都应该是在本身采用的算法没有任何问题情况下的“锦上添花”,而不是“雪中送炭”。
如果下面的说法存在误导,请专业大佬评论指正
读写优化
C++读写优化——解除流绑定
在ACM里,经常出现数据集超大造成 cin TLE的情况,其实cin效率之所以低,不是比C低级,而是因为需要与scanf的缓冲区同步,导致效率降低,而且是C++为了兼容C而采取的保守措施。
C++代码中添加 ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
这一段之后,读取速度即可无限趋近于scanf
和printf
。
如果代码首部没有using namespace std;
则要换成std::ios::sync_with_stdio(0),std::cin.tie(0),std::cout.tie(0);
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
// 未使用using namespace std;时使用下方写法
// std::ios::sync_with_stdio(0),std::cin.tie(0),std::cout.tie(0);
// 代码主体读取、操作、打印
}
std::ios::sync_with_stdio(0)
在 C++ 中,取消同步流(std::ios::sync_with_stdio)是一个常用的技巧,用来加快输入/输出流(I/O)的速度。默认情况下,C++ 的标准库(iostream)与 C 的标准库(stdio)之间是同步的,这意味着它们共享缓冲区,并且每次使用其中一个库的 I/O 功能时,都会刷新另一个库的缓冲区。这保证了数据的一致性,但也增加了性能开销。
通过调用 std::ios::sync_with_stdio(0)
,你可以取消这种同步,这通常会导致 I/O 操作的速度显著提高。但是,一旦取消了同步,就不能再混用 C++ 和 C 的 I/O 函数(如 cin/cout 和 scanf/printf),因为这可能会导致输出顺序不确定或其他问题。
如果已经采用了C++的输入函数cin,就避免再使用C的scanf;同样的如果已经使用 cout 就避免再使用 printf
cin.tie(0)
在默认的情况下cin绑定的是cout,每次执行的时候都要调用flush,这样会增加IO负担。
这行代码解除了 cin(输入流)与 cout(输出流)之间的绑定。默认情况下,cin 与 cout 绑定在一起,这意味着在每次从 cin 读取之前,cout 的缓冲区都会被自动刷新。通过解除绑定,可以进一步提高 I/O 性能,但这也意味着在输出和输入操作之间不再自动刷新 cout 的缓冲区。
cout.tie(0)
这行代码通常不是必须的,因为 cout 默认情况下并不绑定到其他流。它的主要作用是确保 cout 不与任何其他流(例如 cin 或 cerr)绑定。但在大多数情况下,这行代码并不会改变默认行为。
C++换行输出
endl会输出’\n’(\n是转义字符,代表换行),然后立即刷新缓冲区并输出到屏幕上。由于要刷新缓冲区,endl会比\n慢一点,一般不建议使用。以下是endl实现:
template <class _CharT, class _Traits>
inline _LIBCPP_INLINE_VISIBILITY
basic_ostream<_CharT, _Traits>&
endl(basic_ostream<_CharT, _Traits>& __os)
{
__os.put(__os.widen('\n'));
__os.flush();
return __os;
}
C++中换行大多喜欢写 cout << endl;
,然而据acmer和本人赛场亲身经历,这种写法比 cout << '\n;
输出速度要慢许多。当然这不乏出题人的原因,不过为了避免悲剧的发生希望大家还是使用如下两种方法。
- 在代码头部使用宏定义
#define endl '\n'
替换endl - 改掉使用endl的习惯
#include <bits/stdc++.h>
#define endl '\n'
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
// 上方使用了宏定义,代码编译预处理阶段就将endl换成了'\n'
cout << endl;
// 直接输出'\n'
cout << '\n';
}
C/C++自定义快读快写
本人没有亲自使用过,不过是看别人代码中有如此运用。据说C++17后getchar()/putchar()已经被负优化了,未知真假,个人选择使用。
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
inline void write(int x)
{
char F[200];
int tmp=x>0?x:-x;
if(x<0)
putchar('-');
int cnt=0;
while(tmp>0)
{
F[cnt++]=tmp%10+'0';
tmp/=10;
}
while(cnt>0)
putchar(F[--cnt]);
}
Java快读快写
大部分初学Java的人应该是使用如下代码进行Java的读写,不过下面这个代码的读写,在面对大量数据的情况下是比较慢的。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// java.util 包下的读取
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
// Java
System.out.println(n);
sc.close();
}
}
下方的读写代码速度较快,经过实践检验,建议采用。该部分代码经过真实调试,应该是不存在什么问题。
特别提醒!!!如果使用了下方代码中的快速输出,代码最后必须使用out.flush(); 必须使用out.flush(); 必须使用out.flush();
快速读入的代码按需使用,写代码时不一定要全部写,如果在XCPC赛场上使用Java,可以提前写好该模板。
import java.io.*;
/**
* 自定义快读类
*/
class Scanner {
static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
// 字符串快速读入对象
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
public int nextInt() {
try {
st.nextToken();
return (int) st.nval;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public double nextDouble() {
try {
st.nextToken();
} catch (IOException e) {
throw new RuntimeException(e);
}
return st.nval;
}
public float nextFloat() {
try {
st.nextToken();
} catch (IOException e) {
throw new RuntimeException(e);
}
return (float) st.nval;
}
public long nextLong() {
try {
st.nextToken();
} catch (IOException e) {
throw new RuntimeException(e);
}
return (long) st.nval;
}
public String next() {
try {
st.nextToken();
} catch (IOException e) {
throw new RuntimeException(e);
}
return st.sval;
}
// 按行读入字符串
public String readLine() {
String s = null;
try {
s = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return s;
}
}
public class Main {
// 快速输出对象
static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
public static void main(String[] args) {
// 创建自定义的Scanner类
Scanner sc = new Scanner();
/**
* 快读使用案例
*/
int n = sc.nextInt();
double d = sc.nextDouble();
float f = sc.nextFloat();
// 读入字符串(以空格、回车结尾)
String str = sc.next();
// 整行读入字符串(以回车换行结尾)
String line = sc.readLine();
/**
* 快速输出使用案例
*/
out.println(n);
out.println(d);
out.println(f);
out.println(str);
out.println(line);
// 快速输出必须要刷新缓冲区,否则无法输出
out.flush();
}
}
读写样例
其他玄学优化——自行试用
下方玄学,只是部分传言,有些优化的效果似乎并不显著;有时不妨一试。
常用函数优化
inline int abs(int x)
{
int y=x>>31;
return (x+y)^y;
}
inline int max(int x,int y)
{
int m=(x-y)>>31;
return (y&m)|(x&~m);
}
inline int min(int x,int y)
{
int m=(x-y)>>31;
return (y&m|x&~m)^(x^y);
}
inline void swap(int &x,int &y)
{
x^=y,y^=x,x^=y;
}
inline int ave(int x,int y)
{
return (x&y)+((x^y)>>1);
}
变量自增
++i快于i++
取模非常慢,尽量用减法代替
把函数中的循环变量在整个函数开头用register统一定义好
频繁使用的数用register,和inline一个用法,只不过有可能把变量存入CPU寄存器,来减少时间;某些生命周期不重叠的变量合并,减少创建变量空间的时间。
int main()
{
register int i;
for (i = 1; i <= n; ++i)
{
// 逻辑部分
}
for (i = 1; i <= n; ++i)
{
// 逻辑部分
}
/*
下方循环多次使用i
*/
}
减少使用STL,他们的常数特别大
现在大部分OJ平台都会自动开O2优化,所以可能STL常数问题可能也没那么严重,有时候也可以尝试手动开O2优化。据说有些时候可能会出现stl的map反而比自己手写map还快的情况…所以自己看情况吧
// 代码头部预处理指令手动打开O2
#pragma GCC optimize(2)
define比赋值更快
定义数组大小时尽量用奇数
尽量不要用bool,int是最快的
if()else() 语句比三元运算符慢;但if语句比三元运算符快
学会合理使用位运算
比如用它判奇偶性。n&1相当于n%2==1。还有一个操作:
inline void swap(int &x,int &y)
{
x^=y^=x^=y;
}