浅谈OI中常用的卡常技巧(时间效率相关)
近期比赛频频被卡常,特此纪念我挂掉的分数。
读写优化
对于 scanf
语句的优化
使用汇编语言函数 __builtin_scanf
以及 __builtin_printf
。
以 __builtin_
开头的内建函数是直接用汇编语言编写的,自然在理论上会很快。
但是真的有比 scanf
快多少,那还有待考究。
对于 cin
cout
语句的优化
可以关闭流同步。
具体如下:
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
会比常规的 scanf
语句要快一些。
这里有一道题目,当时用了 scanf
导致 TLE
挂了大分,但使用关闭流同步的 cin
就能过。
普通的快读快写
namespace IO{
template<typename T>
inline void read(T &x){
x = 0; char c = getchar(); int f = false;
for(; !isdigit(c); c = getchar()) f |= c == '-';
for(; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0';
if(f) x = -x;
}
template<typename T>
inline void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
基于 fread/fwrite
优化的快读
一般来说 fwrite
用处没有 fread
大。
容易发现,它相较与普通的快读快写,速度有了较大的提升。
namespace IO{//只有fread版本
inline char getchar(){
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2)? EOF : *p1++;
}
template<typename T>
inline void read(T &x){
x = 0; char c = getchar(); int f = false;
for(; !isdigit(c); c = getchar()) f |= c == '-';
for(; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0';
if(f) x = -x;
}
template<typename T>
inline void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
namespace IO{
const int Size = 1 << 20;
namespace IN{
inline char gc(){
static char buf[Size], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, Size, stdin), p1 == p2)? EOF : *p1++;
}
template<typename T>
inline void read(T &x){
x = 0; char c = gc(); int f = false;
for(; !isdigit(c); c = gc()) f |= c == '-';
for(; isdigit(c); c = gc()) x = x * 10 + c - '0';
if(f) x = -x;
}
}
namespace OUT{
char buf[Size << 1], r[20]; int p, p2 = -1;
inline void flush(){
fwrite(buf, 1, p2 + 1, stdout), p2 = -1;
}
inline void pc(char c){buf[++p2] = c;}
inline void write(int x){
if(p2 > Size) flush();
if(x < 0) pc('-'), x = -x;
do{
r[++p] = x % 10 + 48;
} while(x /= 10);
do{
pc(r[p]);
} while(--p);
}
}
}
using IO::IN::gc;
using IO::OUT::pc;
using IO::IN::read;
using IO::OUT::write;
using IO::OUT::flush;
对于上述写法,别忘记在所有输出结束后再 flush
一下。
还用一种基于 steambuf
优化的读写,在大部分情况下实用性未必比 fread
高,这里不作提供。
小 trick
有时候不经意间,我们会这样写:
for(int i = 1; i <= n; ++i){
cout << a[i] << " ";
}
但实际上,我们把字符串 " "
换成单个字符能较大地提升运行效率:
for(int i = 1; i <= n; ++i){
cout << a[i] << ' ';
}
指令集优化
只在部分OJ上可用。
常见的指令集:
#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")
精简版指令集:
#pragma GCC optimize("Ofast,no-stack-protector")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
其它实用优化
把函数变成内联函数。
也即在函数类型前面加 inline
。
就像这样:
inline void dfs(int u){
}
寄存器优化
也即在循环变量的类型前面加 register
。
举个例子:
for(register int i = 1; i <= n; ++i){
for(register int j = 1; j <= n; ++j){
for(register int k = 1; k <= n; ++k){
}
}
}
但事实上,往往只需要在最内层的循环变量前加 register
就能达到一样或者更优的效果。
值得一提的是,在 c++11
之后的语法,编译的时候会忽略所有 register
,甚至有时加了会产生微小的负优化。
循环展开
所谓循环展开就是通过在每次迭代中执行更多的数据操作来减小循环开销的影响。
很多时候能实质性、大幅度减小常数的一个利器。
但是在 O(2)
已至 O(fast)
下优化力度逐渐减少。
常见的形如:
//展开前
for(int i = 1; i <= n; ++i){
c += a[i];
}
//展开后
for(int i = 1; i <= n; i += 2){
c += a[i];
c += a[i + 1];
}
//注:这里假设 a[n + 1] 的值为 0
总结一下,若想要循环展开有实质性优化的必要条件:循环里的语句直接前后没有直接的关联。
比如这样的循环语句,展开了效用就不大:
for(int i = 1; i <= n; ++i){
a *= c;
c += d;
}
- 还有一种原理和他类似的优化:
//优化前
for(int i = 1; i <= n; ++i){
c += a[i];
}
for(int i = 1; i <= n; ++i){
d -= b[i];
}
//优化后
for(int i = 1; i <= n; ++i){
c += a[i];
d -= b[i];
}
define
, constexpr
与 const
在这里,我们探讨用三者来表示一个常量的快慢。
首先,用三者的定义及形式如下:
define
:宏定义,等价于直接写下常量的内容;
#define N 100000
constexpr
:常量。
constexpr int N = 100000;
const
:只读量。
const int N = 100000;
他们和常规变量的运行效率比较:
define > constexpr > const = 变量
故而建议使用 define
或者 constexpr
。
提高 Cache 命中率(内存连续、减少数组嵌套)
-
内存连续
比如以下代码:
//n为偶数 for(int i = 1; i <= n / 2; ++i){ c[i] += v, c[n - i + 1] += v; }
就不如这样来的快:
for(int i = 1; i <= n; ++i){ c[i] += v; }
这说明,我们持续访问连续的内存,会比访问间隔较远的内存来得快。
-
减少数组嵌套调用
例如以下代码段,
for(int i = 1; i <= n; ++i){ for(int j = 1; j <= n; j++){ a[c[b[i]]] = a[c[b[i]]] * 10 + j; } }
不如这样:
for(int i = 1; i <= n; ++i){ int bi = b[i], ci = c[bi]; for(int j = 1; j <= n; j++){ a[ci] = a[ci] * 10 + j; } }
时间效率的提升可观。
运算优化(取模优化)
凡是手写过高精,都容易观察到:加减法要比乘除法快很多。其中,乘法又会比除法快很多。
所以我们有时候可以把一些乘除法给拆成左右移与加减法,例如:
a = b * 10 -> a = (b << 3) + (b << 1);
-
此外,这里还介绍一个经典的取模优化。
在一个程序中,假设
的值很小(其中mod
代表模数), 那么我们就可以使用以下代码大大地减小常数:while(a >= mod) a -= mod;
if-else
与 switch-case
条件语句越多,switch-case
相比于 if-else
就越快。
条件少的时候,switch-case
就会自动转化为 if-else
。
所以在写代码时可以多用后者。
类型优化
众所周知,int
是一个 c++
中相对很常用的类型。尽管它理论上要比 char
, bool
, short
等类型运行的慢,但它实际的运行速度是快于其余类型(甚至远远快于)。
对函数递归的优化
假若在方便的情况下,DFS
尽量写迭代,不要写递归。因为递归本身就意味着常数会相对较大,zkw
线段树(一种非递归线段树)就是靠着非递归常数小而出名的。
二进制压位
仅适用于部分题目。可以手写,也可以使用 STL 模板库里的 bitset
。
其它小优化
-
把数组的大小开成二的幂次少一点点。
部分题目优化力度可观。
-
++i
相比于i++
,不会生成临时变量,故而会更快。 -
优化条件的先后顺序
逻辑与运算符,在它的前项为
时不会去看后项。类似的,对于逻辑或运算符,在它的前项为
时不会去看后项。所以我们把返回值为
false
概率大一点的条件放在逻辑与的前面,返回值为true
的概率大一点条件放在逻辑或前面。具体效用还是要看具体的题目。
-
……
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!