加速板子

机考高阶算法模板:复杂度与示例用法速查

以下整理了机考中应对中等题和难题的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)$ 子串匹配、回文判断、重复子串查找

这些模板的核心价值在于:手写复杂易出错,但作为预存模板可直接复用,在机考中能显著节省时间,专注于解题逻辑而非工具实现。建议结合实际题目练习调用,确保熟练使用。

posted @ 2025-09-07 01:54  .N1nEmAn  阅读(18)  评论(0)    收藏  举报