【个人总结】常用 OI 技巧汇总

严格来说,这可能并不能算作学习笔记,但还是这么算了。

这篇文章可能全程比较沙雕,所以对于风格不要过度在意。


咳咳,上面都是作者的废话。下面进入正题。

这篇文章主要介绍的是一些比较常用(?)的 OI 小技巧。这可能有助于在 OI 中获得优势(?\(\times 2\))。

文章内容按照字典序排序。(啥时候想到就写一下吧)


\(\textrm{I}\). 龟速乘

这是一个极其常用的小技巧,原因比较简单,那就是比赛时常用。

通常来说,一道题的答案如果过大的话,有两种方案——高精度和取模。

但是,由于前一段时间(?\(\times 3\))的接连不断的高精度把人弄得有一点烦,加上高精度本身也有复杂度一说,所以并不是一个理想的评测算法的工具。

取而代之,很多题目都会在题目末尾加上一句「答案对 \(998244353\) 取模」或类似的话。一来是解决内置变量存储上限,二来则是忽略计算复杂度。

但是,当模数极其接近变量上限边缘时,乘法就变得岌岌可危,比如:计算 \(123456789876\times 5432123456789 \bmod{192837465647389}\) 这种类似的东西。

咳咳,于是乎,我们就发明了一种很棒的东西。这可以解决模数上限的问题,但不可避免地会出现一点常数,虽然只有 \(O(\log n)\)。这就是龟速乘。

那说了这么多,龟速乘到底是个什么东西呢?????说白了就是快速幂。为什么?

多次连乘是乘方运算,多次连加就是乘法运算

也就是说吧乘法变成一次次的加法而达到其目的,也就不可避免地有 \(O(\log n)\) 的多余复杂度。

但是!这并不是唯一的解决办法,网上还有通过 long double 来卡精度从而达到 \(O(1)\) 的龟速乘,这里不再赘述。


\(\textrm{II}\). 快速幂

快速幂是位运算的应用中的一种,可以在 \(O(\log n)\) 的时间内计算形如 \(a^b\ (b\in \mathbb{N}^{*})\) 的算法。

简单来说,就是计算一个数列 \(\left<c_0,c_1,c_2,\cdots,c_k\right>\),使得 \(b=\sum\limits_{i=0}^k \left(c_i\times 2^i\right)\),这可以通过二进制分解在 \(O(\log n)\) 的时间内解决。

接下来是算法的精髓——注意到 \(2^i=2^{i-1} + 2^{i-1}\),即 \(u^{2^i}=\left(u^{2^{i-1}}\right)^2\)

\(\therefore\quad a^b=\prod\limits_{i=0}^k a^{2^i}\)

而预处理 \(a^{2^i}\)\(O(k)\sim O(\log n)\),总体就是 \(O(\log n)\)


Updated on 2020-08-27

\(\textrm{III}\). 快读/快写

顾名思义,快读是一种加快读入速度的技巧。经实践比 cin 快一倍。如果使用 fread(俗称「光速读」),在 \(10^7\) 级别的整数读入时可以节省将近 \(60\ \textrm{ms}\) 的时间。

快读的核心操作就是减少判断来达到比 scanf 更快的效果。一般来说,快读的步骤是这样的:

  1. 读入字符,直到发现数字或是负号;
  2. 如果读入了负号,就打上标记;
  3. 读入这个数,有标记的把标记打上。

所以,实现大概就是那个样子:

inline int read()
{
	int ch = getchar(), t = 1, n = 0;
	while (ch < '0' || ch > '9') { ch = getchar(); if (ch == '-') t = -1; }
	while (ch >= '0' && ch <= '9') { n = n * 10 + ch - '0', ch = getchar(); }
	return n * t;
}

当然,作为一项卡常神器,快读的奇特优化必然是少不了,接下来介绍几个比较常用的优化:

1. 库函数优化

用 ctype.h 内的 isspaceisdigit 函数来取代判断部分,这样可以很大程度上减少判断。

当然,如果题目中的数给定的是自然数就可以省去负数的判断。

2. 位运算优化

用异或和位移来加速加减法的优化。在某种意义上有一点作用,但是相比于下面这个就微不足道了 hh——

3.(重头戏)fread 读入优化

就是提前大块读入,然后做快读时直接调用数组。经测试是一种极其优秀的优化。

但由于其编写麻烦,一般只用于范围是 \(10^7\) 级别的 IO。

这里是一道用来卡常快读的题目,用你的快读把这道题 A 了吧!


当然,我们也可以实现类似功能的快写。但是由于类似的功能不常用,所以一般不用大优化。

放一下模板(一般都是递归写的,想要手写栈也没事):

void _write(int x)
{
	if (x > 9)
		_write(x);
	putchar('0' + x % 10);
}

inline void write(int x)
{
	if (x < 0)
		putchar('-'), x = -x;
	_write(x);
}

\(\textrm{IV}\). DFS 求拓扑序

没错,你没听错,拓扑序可以用 DFS 来求。这个神秘的技巧可以在 Tarjan 缩点后 DAG DP 中帮助省掉一大堆代码。

结论是:DFS 的出栈序是反图的拓扑序。自行手玩感性理解。

posted @ 2020-07-28 14:10  5ab  阅读(1083)  评论(0编辑  收藏  举报