卡常的一些奇妙操作

卡常数,又称底层常数优化,是信息学竞赛中一种针对程序基本操作进行空间或时间上优化的行为,与时间复杂度或剪枝有别。——百度百科

这一期,我们来聊一聊令 OIer 们万分头疼的卡常,其通常考察的是选手对计算机原理的了解程度,而非算法的掌握程度或熟练程度。这里就不得不提到著名的卡常数选手王逸松,在他命题的 WC2017 中惊现宗师级题目,令无数选手苦思冥想,不得其解,由此 O(wys) 的优秀复杂度求由此得名。

那么,如何才能让自己的代码又快又好,并且透露出大佬般的风格 呢?下面介绍几种方法。(谅本蒟蒻才疏学浅,大佬勿喷)

register 关键字的使用

register 作用是将其加在变量声明前可以建议编译器将变量直接放入寄存器中,从而大量减少 CPU 从内存频繁读取的时间,从而加快程序运行速度。
通常,register 只添加在循环变量等需要经常调用的变量前,由于 CPU 中寄存器数量有限,并不能保证变量一定被放入寄存器,添加过度还可能适得其反。
例:

int sum = 0;
for (register int i = 1; i <= 100; i ++) {
	sum += i;
}
cout << sum << endl;

声明:register 自从 C++11 开始弃用,C++17 被废除,因此在正式比赛中不会起到优化作用

inline 内联函数的使用

在函数声明或定义中,函数返回类型前加上关键字 inline,即可以把函数指定为内联函数。这样可以解决一些频繁调用的函数大量消耗栈空间(栈内存)的问题。
同样的,不建议无脑地在所有函数之前添加,可能导致负优化。(未亲测)
例:

inline long long fac (int x) {
	if (x == 1)
		return 1;
	else
		return fac(x - 1) * x;
}

位运算的使用

比赛或练习的过程中,试着使用位运算代替乘法、除法和取模运算,程序运行效率会大大提升。

  1. a * 2 可改为 a << 1
  2. a / 2 可改为 a >> 1
  3. a % n 当 n 为 2 的整数次幂时可改为 a & (n - 1)
  4. a + 1 当 a 为偶数时甚至可以写成 a | 1
  5. 交换两个数的改写方法
int a, b;
a = a ^ b;
b = a ^ b;
a = a ^ b;
  1. 我见过一些大佬快读中 a * 10 写成 (a << 3) + (a << 1),但我觉得可能不大实用

分支语句的改进

经常用用三目运算符,switch case 语句,而非 if else 来提升效率和减少代码量
例:

int n;
cin >> n;
n % 2 == 0 ? cout << "Yes" << endl : cout << "No" << endl;
int a, b;
char ch;
cin >> a >> b >> ch;
switch (ch) {
	case '+': cout << a + b << endl; break;
	case '-': cout << a - b << endl; break;
	case '*': cout << a * b << endl; break;
	case '/': cout << a / b << endl; break;
}

输入输出的改进

cincout 因它们的简洁易懂深受初学者们的喜爱,但由于只把数据输到缓冲区,导致效率降低,面对比赛时数 MB 的输入文件,还是难免 TLE
所以,可以在主函数的开头加上三行,提升效率,或者采用更高明的输入输出方式。

ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
scanf 与 printf

格式化输入输出,较 cincout 略胜一筹。

int a;
scanf("%d", &a);
printf("%d", a);
快读与快写

借助于 getchar() 的高速输入速度提高效率,代码来源-一位洛谷大佬的博客

inline int read()
{
	register int s=0,w=1;
	register char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') 
	{
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s*w;
}
inline void write(int x)
{
	if(x<0)
	{
    	putchar('-');
		x=-x;
	}
    if(x>9)
		write(x/10);
    putchar(x%10+'0');
}

一些小细节

  1. 循环中使用 ++ i 而不是 i ++,据说前者的汇编代码只有两行,而后者有三行
  2. 变量赋初值时 a(1)a = 1 更快
  3. 数组开奇数个大小,而不是偶数个(未亲测)
  4. 循环尽量展开,即减少循环次数,如“松式基排”
  5. 一些不会更改的变量或数组定义成常量
  6. 多用 while 少用 for
  7. 少用 STL ,但在某些 O2 场合效率会提高

最后的最后,祝愿 OIer 们 骗分过样例,暴力出奇迹,暴搜挂着机,打表出省一

本文在洛谷同步发表,转载请附链接https://www.cnblogs.com/huishou1981/p/15140733.html

posted @ 2021-08-14 14:06  Programmer-sun  阅读(594)  评论(0编辑  收藏  举报