加速板子
机考高阶算法模板:复杂度与示例用法速查
以下整理了机考中应对中等题和难题的4个核心高阶模板,包含功能、复杂度、示例代码及应用场景,适合开卷考试快速查阅。
一、【I/O外挂】高速读写模板
功能
解决大数据量($10^6$级别)输入输出时的效率问题,避免因cin/cout速度慢导致的TLE(超时)。
复杂度
- 单字符处理:$O(1)$
- 整体效率:比默认
cin快5-10倍,接近scanf/printf。
示例代码
// 方法1:轻量版(关闭同步流,保留cin/cout用法)
#include <iostream>
using namespace std;
int main() {
// 放在main函数开头,仅需一次
ios_base::sync_with_stdio(false); // 解除cin与stdio同步
cin.tie(NULL); // 解除cin与cout绑定(可进一步提速)
// 之后正常使用cin/cout
int n;
cin >> n;
for (int i = 0; i < n; ++i) {
int x;
cin >> x;
cout << x << " ";
}
return 0;
}
// 方法2:彻底版(自定义快读函数,处理整型)
#include <cstdio> // 依赖getchar()
inline int read() {
int x = 0, f = 1; // f记录符号(1为正,-1为负)
char ch = getchar(); // 读取单个字符
// 跳过非数字字符(处理负号)
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
// 读取数字部分
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0'); // 累加数字(ch-'0'转成整型)
ch = getchar();
}
return x * f; // 返回带符号的结果
}
#include <cstdio>
// 读取 long long 类型的快读函数
inline long long read_ll() {
// 1. 将 x 类型改为 long long,避免溢出
long long x = 0;
int f = 1; // 符号位,正数为1,负数为-1
char ch = getchar();
// 处理符号('-'开头则标记为负数)
while (ch < '0' || ch > '9') {
if (ch == '-') f = -1;
ch = getchar();
}
// 逐字符转换为数字(逻辑不变,仅 x 是 long long)
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0'); // 此处 x*10 不会溢出(long long 支持到 9e18)
ch = getchar();
}
return x * f; // 返回带符号的 long long 值
}
int main() {
long long a = read_ll();
long long b = read_ll();
printf("%lld\n", a + b); // 输出用 %lld 匹配 long long
return 0;
}
// 使用示例
int main() {
int n = read(); // 替代cin >> n
for (int i = 0; i < n; ++i) {
int x = read();
printf("%d ", x); // 配合printf输出更快
}
return 0;
}
应用场景
- 输入数据量超过$10^5$(如1e6个整数);
- 题目时间限制严格(如1秒内处理1e6级数据);
- 字符串输入量极大时(可扩展快读函数支持字符串)。
字符串
不带空格
#include <cstdio>
#include <string>
using namespace std;
// 快读:读取不含空格/换行/制表符的字符串(遇到空白字符停止)
inline string read_str() {
string s;
char ch;
// 第一步:跳过所有空白字符(空格、换行、制表符'\t')
while ((ch = getchar()) == ' ' || ch == '\n' || ch == '\t') {
// 什么都不做,直到遇到非空白字符
}
// 第二步:读取所有非空白字符,直到再次遇到空白字符
while (ch != ' ' && ch != '\n' && ch != '\t' && ch != EOF) {
s.push_back(ch); // 将字符加入字符串
ch = getchar(); // 读取下一个字符
}
return s;
}
int main() {
int n = read_ll(); // 复用之前的LL快读函数
while (n--) {
string s = read_str(); // 读"apple"/"banana"/"cherry"
int val = read_ll(); // 读100/200/300
printf("%s %d\n", s.c_str(), val); // 快写输出
}
return 0;
}
带空格
#include <cstdio>
#include <string>
using namespace std;
// 快读:读取一整行(含空格),直到遇到换行符停止(换行符不存入字符串)
inline string read_line() {
string s;
char ch;
// 读取字符,直到遇到换行符'\n'或文件结束EOF
while ((ch = getchar()) != '\n' && ch != EOF) {
s.push_back(ch);
}
return s;
}
// 【可选】如果前面用了整数快读(如read_ll()),需先吃掉残留的换行符
inline void eat_newline() {
char ch;
while ((ch = getchar()) == '\n' || ch == ' ') {
// 吃掉换行和多余空格
}
// 把吃掉的非换行字符塞回缓冲区(避免影响后续读取)
if (ch != EOF) {
ungetc(ch, stdin);
}
}
int main() {
int n = read_ll(); // 读2,此时缓冲区残留一个换行符
eat_newline(); // 吃掉换行符,避免影响后续read_line()
while (n--) {
string line = read_line(); // 读整行"My salary..."
printf("%s\n", line.c_str());
}
return 0;
}
二、【图论神器】并查集(DSU)
功能
高效处理元素的「合并」与「查询是否同属一集合」,核心用于解决图连通性问题。
复杂度
- 带路径压缩和按秩合并优化后,单次
find/unite操作近乎$O(1)$(严格为$O(\alpha(n))$,$\alpha$为反阿克曼函数,增长极慢)。
示例代码
#include <vector>
#include <numeric> // 含iota函数(初始化父节点)
struct DSU {
std::vector<int> parent; // 父节点数组
std::vector<int> rank; // 秩(用于按秩合并,优化树高)
// 构造函数:初始化n个独立元素(1-based索引)
DSU(int n) {
parent.resize(n + 1);
rank.resize(n + 1, 1); // 初始秩为1
// 每个元素的父节点初始为自身(0-based可去掉+1)
std::iota(parent.begin(), parent.end(), 0);
}
// 查找根节点 + 路径压缩(核心优化)
int find(int x) {
if (parent[x] != x) {
// 递归找到根节点后,将x的父节点直接指向根(压缩路径)
parent[x] = find(parent[x]);
}
return parent[x];
}
// 合并两个集合 + 按秩合并(核心优化)
void unite(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) return; // 已在同一集合
// 秩小的树合并到秩大的树(减少树高)
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else {
parent[rootY] = rootX;
if (rank[rootX] == rank[rootY]) {
rank[rootX]++; // 秩相等时,合并后根的秩+1
}
}
}
// 判断两个元素是否连通
bool connected(int x, int y) {
return find(x) == find(y);
}
// 统计连通块数量(可选功能)
int countSets() {
int cnt = 0;
for (int i = 1; i < parent.size(); ++i) {
if (find(i) == i) cnt++; // 根节点数量即连通块数
}
return cnt;
}
};
// 使用示例
int main() {
DSU dsu(5); // 5个元素:1-5
dsu.unite(1, 2);
dsu.unite(3, 4);
dsu.unite(2, 3);
bool isConnected = dsu.connected(1, 4); // true(1-2-3-4连通)
int sets = dsu.countSets(); // 2({1,2,3,4}, {5})
return 0;
}
应用场景
- 图的连通性问题(如判断两点是否连通、统计连通块数量);
- Kruskal算法求最小生成树(判断边是否形成环);
- 等价类划分(如合并用户组、朋友圈问题)。
三、【区间利器】树状数组(BIT)
功能
高效处理「单点修改」和「区间查询」(求和、前缀和等),代码简洁、常数小。
复杂度
- 单点修改:$O(\log n)$
- 区间查询:$O(\log n)$
示例代码
#include <vector>
#include <iostream>
using namespace std;
struct BIT {
vector<int> tree; // 树状数组本体(1-based索引)
int n; // 数组大小
// 构造函数:初始化大小为size的BIT
BIT(int size) : n(size), tree(size + 1, 0) {}
// 核心:lowbit函数(返回x二进制最低位1的值)
int lowbit(int x) {
return x & (-x); // 如6(110)的lowbit是2(10)
}
// 单点修改:在index位置增加val(index从1开始)
void update(int index, int val) {
for (int i = index; i <= n; i += lowbit(i)) {
tree[i] += val;
}
}
// 查询前缀和:[1..index]的和
int query(int index) {
int sum = 0;
for (int i = index; i > 0; i -= lowbit(i)) {
sum += tree[i];
}
return sum;
}
// 查询区间和:[left..right]的和(left、right从1开始)
int queryRange(int left, int right) {
return query(right) - query(left - 1);
}
};
// 使用示例
int main() {
vector<int> nums = {1, 2, 3, 4, 5}; // 原数组(0-based)
int n = nums.size();
BIT bit(n);
// 初始化:将nums的值插入BIT(1-based)
for (int i = 0; i < n; ++i) {
bit.update(i + 1, nums[i]);
}
// 查询[2..4]的和(对应nums[1]到nums[3]:2+3+4=9)
int sum = bit.queryRange(2, 4);
cout << sum << endl; // 输出9
// 修改:nums[2](即index=3)增加2(3→5)
bit.update(3, 2);
// 再次查询[2..4]:2+5+4=11
sum = bit.queryRange(2, 4);
cout << sum << endl; // 输出11
return 0;
}
应用场景
- 频繁单点更新并查询区间和(如动态统计数组某段和);
- 逆序对问题(配合离散化);
- 前缀和相关的动态查询(如统计前缀中元素出现次数)。
四、【字符串利器】字符串哈希(Rolling Hash)
功能
将字符串映射为整数,实现$O(1)$时间判断两个子串是否相等,替代KMP解决字符串匹配问题。
复杂度
- 预处理:$O(n)$(n为字符串长度)
- 子串哈希查询:$O(1)$
示例代码
#include <string>
#include <vector>
using namespace std;
struct StringHash {
using ULL = unsigned long long; // 无符号长整型(自动溢出取模)
ULL base = 131; // 哈希基数(常用质数131、13331,减少冲突)
ULL mod = 1e18 + 3; // 可选大质数模(进一步减少冲突)
vector<ULL> h; // 前缀哈希数组:h[i]是前i个字符的哈希值
vector<ULL> p; // 幂次数组:p[i] = base^i
// 构造函数:预处理字符串s的哈希
StringHash(const string& s) {
int n = s.size();
h.resize(n + 1, 0);
p.resize(n + 1, 1); // p[0] = 1(base^0 = 1)
for (int i = 1; i <= n; ++i) {
p[i] = p[i - 1] * base; // 计算base的i次方
// 哈希公式:h[i] = h[i-1] * base + s[i-1](s[i-1]是第i个字符)
h[i] = h[i - 1] * base + s[i - 1];
}
}
// 获取子串s[left..right]的哈希值(1-based,left和right从1开始)
ULL getHash(int left, int right) {
// 公式:h[right] - h[left-1] * p[right - left + 1]
return h[right] - h[left - 1] * p[right - left + 1];
}
};
// 使用示例
int main() {
string s = "abcabcabc";
StringHash hash(s);
// 判断子串s[1..3]("abc")与s[4..6]("abc")是否相等
bool isEqual1 = (hash.getHash(1, 3) == hash.getHash(4, 6)); // true
// 判断子串s[1..2]("ab")与s[7..8]("ab")是否相等
bool isEqual2 = (hash.getHash(1, 2) == hash.getHash(7, 8)); // true
return 0;
}
应用场景
- 快速判断两个子串是否相同(如重复子串查找);
- 统计不同子串的数量;
- 回文子串判断(配合反向哈希);
- 字符串匹配(如在文本中查找模式串出现的位置)。
总结
| 模板名称 | 核心优势 | 核心复杂度 | 高频应用场景 |
|---|---|---|---|
| 高速读写 | 解决大数据量IO超时 | $O(1)$/字符 | $10^5$级别以上输入输出 |
| 并查集 | 高效处理集合合并与查询 | 近乎$O(1)$ | 连通性问题、Kruskal算法 |
| 树状数组 | 动态区间查询与单点修改 | $O(\log n)$ | 区间和、逆序对、动态统计 |
| 字符串哈希 | 快速子串匹配与比较 | 预处理$O(n)$,查询$O(1)$ | 子串匹配、回文判断、重复子串查找 |
这些模板的核心价值在于:手写复杂易出错,但作为预存模板可直接复用,在机考中能显著节省时间,专注于解题逻辑而非工具实现。建议结合实际题目练习调用,确保熟练使用。
浙公网安备 33010602011771号