算法题知识点总结与代码模板

算法题知识点总结与代码模板

基于湖南省赛 CSG 题库整理,共 149 道题目
所有模板均为 C++ 竞赛常用写法


目录

  1. 图论
  2. 动态规划
  3. 字符串
  4. 数据结构
  5. 数学与数论
  6. 计算几何
  7. 搜索与博弈
  8. 贪心
  9. 二分与三分
  10. 附录:题目知识点速查表

一、图论

1.1 Dijkstra 最短路(堆优化)

适用场景: 单源最短路,非负权边

const int INF = 0x3f3f3f3f;
vector<pair<int, int>> g[N];  // to, weight
int dist[N];

void dijkstra(int s, int n) {
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
    memset(dist, 0x3f, sizeof(dist[0]) * (n + 1));
    dist[s] = 0;
    pq.push({0, s});
    while (!pq.empty()) {
        auto [d, u] = pq.top(); pq.pop();
        if (d > dist[u]) continue;
        for (auto [v, w] : g[u]) {
            if (dist[v] > dist[u] + w) {
                dist[v] = dist[u] + w;
                pq.push({dist[v], v});
            }
        }
    }
}

思路要点:

  • 每次选距离最小的点扩展(贪心)
  • 用优先队列(小根堆)优化选点
  • 出队时若距离已更新则跳过(懒惰删除)
  • 时间复杂度 O((V+E) log V)

相关题目: CSG 1052, 1109, 1116, 1137, 1139, 1190, 1193


1.2 Kruskal 最小生成树

适用场景: 稀疏图求MST

struct Edge { int u, v, w; };
vector<Edge> edges;

// 并查集
int fa[N];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }

int kruskal(int n) {
    sort(edges.begin(), edges.end(), [](Edge& a, Edge& b) { return a.w < b.w; });
    for (int i = 1; i <= n; i++) fa[i] = i;
    int res = 0, cnt = 0;
    for (auto& e : edges) {
        int u = find(e.u), v = find(e.v);
        if (u != v) {
            fa[u] = v;
            res += e.w;
            if (++cnt == n - 1) break;
        }
    }
    return res;  // cnt < n-1 则不连通
}

思路要点:

  • 边按权值从小到大排序
  • 依次加边,用并查集判环
  • 选满 n-1 条边结束
  • 时间复杂度 O(E log E)

相关题目: CSG 1099, 1100


1.3 LCA 最近公共祖先(倍增法)

适用场景: 树上两点最近公共祖先、路径查询

vector<int> g[N];
int depth[N], up[20][N];  // up[k][u] 表示u的2^k级祖先

void dfs(int u, int fa) {
    up[0][u] = fa;
    for (int i = 1; i < 20; i++)
        up[i][u] = up[i-1][up[i-1][u]];
    for (int v : g[u]) {
        if (v == fa) continue;
        depth[v] = depth[u] + 1;
        dfs(v, u);
    }
}

int lca(int u, int v) {
    if (depth[u] < depth[v]) swap(u, v);
    // 跳到同一深度
    int diff = depth[u] - depth[v];
    for (int i = 0; i < 20; i++)
        if (diff >> i & 1) u = up[i][u];
    if (u == v) return u;
    for (int i = 19; i >= 0; i--)
        if (up[i][u] != up[i][v])
            u = up[i][u], v = up[i][v];
    return up[0][u];
}

思路要点:

  • DFS预处理深度和倍增表
  • 先将两点跳到同一深度
  • 从大到小枚举二进制位,同时上跳
  • 时间:预处理 O(n log n),查询 O(log n)

相关题目: CSG 1077, 1088, 1129, 1142, 1217, 1322


1.4 Dinic 最大流

适用场景: 最大流、最小割、二分图匹配

struct Edge { int to, rev; long long cap; };
vector<Edge> g[N];
int level[N], iter[N];

void add_edge(int from, int to, long long cap) {
    g[from].push_back({to, (int)g[to].size(), cap});
    g[to].push_back({from, (int)g[from].size()-1, 0});
}

void bfs(int s) {
    memset(level, -1, sizeof level);
    queue<int> q; level[s] = 0; q.push(s);
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (auto& e : g[u]) {
            if (e.cap > 0 && level[e.to] < 0) {
                level[e.to] = level[u] + 1;
                q.push(e.to);
            }
        }
    }
}

long long dfs(int u, int t, long long f) {
    if (u == t) return f;
    for (int& i = iter[u]; i < g[u].size(); i++) {
        Edge& e = g[u][i];
        if (e.cap > 0 && level[u] < level[e.to]) {
            long long d = dfs(e.to, t, min(f, e.cap));
            if (d > 0) {
                e.cap -= d;
                g[e.to][e.rev].cap += d;
                return d;
            }
        }
    }
    return 0;
}

long long max_flow(int s, int t) {
    long long flow = 0;
    while (true) {
        bfs(s);
        if (level[t] < 0) return flow;
        memset(iter, 0, sizeof iter);
        long long f;
        while ((f = dfs(s, t, 1e18)) > 0) flow += f;
    }
}

思路要点:

  • BFS 建分层图(level数组)
  • DFS 找阻塞流(当前弧优化 iter)
  • 重复 BFS+DFS 直到无法增广
  • 时间复杂度 O(E * V²),单位容量网络 O(E√V)

相关题目: CSG 1062, 1189


📈 二、动态规划

📈 2.1 最长上升子序列(O(n log n))

适用场景: LIS长度、LIS计数相关

int lis(vector<int>& a) {
    vector<int> tails;
    for (int x : a) {
        auto it = lower_bound(tails.begin(), tails.end(), x);
        if (it == tails.end()) tails.push_back(x);
        else *it = x;
    }
    return tails.size();
}

思路要点:

  • tails[i] 表示长度为 i+1 的LIS末尾最小元素
  • 每个数二分找到替换位置
  • 最终 tails 长度就是 LIS 长度
  • 注意:求最长不下降用 upper_bound

相关题目:

  • CSG 1138

📦 2.2 01 背包

适用场景: 每件物品选或不选

// 01背包:n件物品,体积V,求最大价值
int dp[N];  // dp[j] 表示体积j的最大价值

void knapsack01(int n, int V, int w[], int v[]) {
    memset(dp, 0, sizeof dp);
    for (int i = 0; i < n; i++)
        for (int j = V; j >= w[i]; j--)  // 倒序!
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}

// 完全背包:每件可选无限次(正序遍历)
void knapsack_full(int n, int V, int w[], int v[]) {
    memset(dp, 0, sizeof dp);
    for (int i = 0; i < n; i++)
        for (int j = w[i]; j <= V; j++)  // 正序!
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}

思路要点:

  • 01背包倒序:保证每件只选一次
  • 完全背包正序:可重复选
  • 滚动数组优化:体积大但价值小时优化空间
  • 常见变种:恰好装满、方案数、多重背包(二进制拆分)

相关题目:

  • CSG 1043
  • CSG 1044
  • CSG 1163

📏 2.3 区间 DP

适用场景: 石子合并、回文串、矩阵链乘

// 石子合并:dp[l][r] 合并区间[l,r]的最小代价
int dp[N][N], sum[N];

int interval_dp(int a[], int n) {
    for (int i = 1; i <= n; i++) sum[i] = sum[i-1] + a[i];
    memset(dp, 0x3f, sizeof dp);
    for (int i = 1; i <= n; i++) dp[i][i] = 0;
    for (int len = 2; len <= n; len++)          // 区间长度
        for (int l = 1; l + len - 1 <= n; l++) { // 左端点
            int r = l + len - 1;
            for (int k = l; k < r; k++)         // 枚举分割点
                dp[l][r] = min(dp[l][r], dp[l][k] + dp[k+1][r] + sum[r] - sum[l-1]);
        }
    return dp[1][n];
}

思路要点:

  • 外层枚举区间长度,内层枚举左端点
  • 枚举分割点 k,合并左右子区间
  • 时间复杂度 O(n³)
  • 四边形不等式可优化到 O(n²)

相关题目:

  • CSG 1069, 1144

📊 2.4 数位 DP(记忆化搜索)

适用场景: 统计 [1,n] 中满足某数位条件的数的个数

long long dp[20][10][2];  // 位置、状态、紧约束
int digit[20];

long long dfs(int pos, int state, bool tight, bool lead, vector<int>& d) {
    if (pos == -1) return check(state);  // 检查是否合法
    if (!tight && !lead && dp[pos][state] != -1) return dp[pos][state];
    int upper = tight ? d[pos] : 9;
    long long res = 0;
    for (int i = 0; i <= upper; i++) {
        res += dfs(pos-1, new_state(state, i),
                   tight && i == upper, lead && i == 0, d);
    }
    if (!tight && !lead) dp[pos][state] = res;
    return res;
}

long long solve(long long x) {
    int len = 0;
    while (x) { digit[len++] = x % 10; x /= 10; }
    memset(dp, -1, sizeof dp);
    return dfs(len-1, 0, true, true, digit);
}

思路要点:

  • 按位处理,记忆化搜索实现
  • 关键参数:位置、状态、紧约束、前导零
  • 紧约束(tight):前面都贴着上限,当前位不能超
  • 前导零(lead):前导零不算入数位统计

相关题目: CSG 1048, 1072


📦 2.5 状态压缩 DP

适用场景: n ≤ 20 的集合类问题

// TSP:dp[mask][i] 经过mask集合,当前在i点
int dp[1<<20][20];

int tsp(int n, int dist[][20]) {
    memset(dp, 0x3f, sizeof dp);
    dp[1][0] = 0;  // 从0出发
    for (int mask = 1; mask < (1<<n); mask++) {
        for (int i = 0; i < n; i++) {
            if (!(mask >> i & 1)) continue;
            for (int j = 0; j < n; j++) {
                if (mask >> j & 1) continue;
                dp[mask | (1<<j)][j] = min(dp[mask | (1<<j)][j],
                                           dp[mask][i] + dist[i][j]);
            }
        }
    }
    return dp[(1<<n)-1][n-1];
}

思路要点:

  • 用二进制位表示集合状态
  • 枚举 mask,枚举当前点,转移到下一个点
  • 时间复杂度 O(n² * 2ⁿ)
  • n 超过 20 基本不可行

相关题目:

  • CSG 1087, 1194, 1195

📜 三、字符串

3.1 KMP / 前缀函数

适用场景: 模式匹配、周期串、前缀后缀相关

// 前缀函数:pi[i] = s[0..i]最长相等真前缀后缀长度
vector<int> get_pi(string& s) {
    int n = s.size();
    vector<int> pi(n, 0);
    for (int i = 1; i < n; i++) {
        int j = pi[i-1];
        while (j > 0 && s[i] != s[j]) j = pi[j-1];
        if (s[i] == s[j]) j++;
        pi[i] = j;
    }
    return pi;
}

// KMP匹配:返回t中所有p出现的位置
vector<int> kmp(string& t, string& p) {
    vector<int> pi = get_pi(p);
    vector<int> res;
    int j = 0;
    for (int i = 0; i < t.size(); i++) {
        while (j > 0 && t[i] != p[j]) j = pi[j-1];
        if (t[i] == p[j]) j++;
        if (j == p.size()) {
            res.push_back(i - j + 1);
            j = pi[j-1];
        }
    }
    return res;
}

思路要点:

  • 前缀函数利用已匹配信息,避免暴力回溯
  • 核心:失配时跳到 pi[j-1] 继续比较
  • 时间复杂度 O(n + m)

相关题目: CSG 1199, 1215, 1328


3.2 📚 Trie 字典树

适用场景: 前缀查询、字符串统计、异或最值

struct Trie {
    int ch[N][26], cnt[N], idx;
    Trie() { idx = 1; }
    
    void insert(string& s) {
        int u = 0;
        for (char c : s) {
            int v = c - 'a';
            if (!ch[u][v]) ch[u][v] = idx++;
            u = ch[u][v];
        }
        cnt[u]++;
    }
    
    int query(string& s) {
        int u = 0;
        for (char c : s) {
            int v = c - 'a';
            if (!ch[u][v]) return 0;
            u = ch[u][v];
        }
        return cnt[u];
    }
};

思路要点:

  • 每个节点代表一个前缀
  • 根节点为空串,路径上的字符组成字符串
  • 插入、查询都是 O(len)
  • 01Trie 可求最大异或和

相关题目: CSG 1061, 1166, 1055


📦 四、数据结构

4.1 线段树(懒标记)

适用场景: 区间修改、区间查询(和、最值等)

struct SegTree {
    long long sum[N << 2], lazy[N << 2];
    
    void push_up(int p) {
        sum[p] = sum[p<<1] + sum[p<<1|1];
    }
    
    void push_down(int p, int l, int r) {
        if (lazy[p]) {
            int mid = (l + r) >> 1;
            sum[p<<1] += lazy[p] * (mid - l + 1);
            sum[p<<1|1] += lazy[p] * (r - mid);
            lazy[p<<1] += lazy[p];
            lazy[p<<1|1] += lazy[p];
            lazy[p] = 0;
        }
    }
    
    void build(int p, int l, int r, int a[]) {
        if (l == r) { sum[p] = a[l]; return; }
        int mid = (l + r) >> 1;
        build(p<<1, l, mid, a);
        build(p<<1|1, mid+1, r, a);
        push_up(p);
    }
    
    void update(int p, int l, int r, int ul, int ur, int val) {
        if (ul <= l && r <= ur) {
            sum[p] += val * (r - l + 1);
            lazy[p] += val;
            return;
        }
        push_down(p, l, r);
        int mid = (l + r) >> 1;
        if (ul <= mid) update(p<<1, l, mid, ul, ur, val);
        if (ur > mid) update(p<<1|1, mid+1, r, ul, ur, val);
        push_up(p);
    }
    
    long long query(int p, int l, int r, int ql, int qr) {
        if (ql <= l && r <= qr) return sum[p];
        push_down(p, l, r);
        int mid = (l + r) >> 1;
        long long res = 0;
        if (ql <= mid) res += query(p<<1, l, mid, ql, qr);
        if (qr > mid) res += query(p<<1|1, mid+1, r, ql, qr);
        return res;
    }
};

思路要点:

  • 每个节点代表一个区间,二叉树结构
  • 懒标记:延迟下传修改,整块更新时打标记
  • 查询/更新到子节点前必须 push_down
  • 时间复杂度 O(log n) 单次操作

相关题目: CSG 1045, 1086, 1191, 1217


4.2 树状数组(Fenwick Tree)

适用场景: 单点修改、前缀查询(和、最值等)

struct Fenwick {
    long long tree[N];
    int n;
    
    void init(int size) { n = size; memset(tree, 0, sizeof tree); }
    
    void add(int i, long long x) {
        for (; i <= n; i += i & -i)
            tree[i] += x;
    }
    
    long long query(int i) {
        long long res = 0;
        for (; i > 0; i -= i & -i)
            res += tree[i];
        return res;
    }
    
    long long range_query(int l, int r) {
        return query(r) - query(l - 1);
    }
};

思路要点:

  • lowbit = i & -i,取最低位的1
  • 加操作:向上跳,累加
  • 查询操作:向下跳,求和
  • 空间小、常数小,功能弱于线段树
  • 时间复杂度 O(log n)

相关题目: CSG 1086, 1094


4.3 并查集(DSU)

适用场景: 连通性、合并集合、Kruskal

struct DSU {
    int fa[N], sz[N];
    
    void init(int n) {
        for (int i = 1; i <= n; i++) fa[i] = i, sz[i] = 1;
    }
    
    int find(int x) {
        return fa[x] == x ? x : fa[x] = find(fa[x]);  // 路径压缩
    }
    
    bool unite(int x, int y) {
        x = find(x), y = find(y);
        if (x == y) return false;
        if (sz[x] < sz[y]) swap(x, y);  // 按秩合并
        fa[y] = x;
        sz[x] += sz[y];
        return true;
    }
    
    bool same(int x, int y) {
        return find(x) == find(y);
    }
};

思路要点:

  • 路径压缩:查询时顺便把父节点指向根
  • 按秩合并:小集合挂到大集合上
  • 近似常数时间复杂度
  • 可扩展:带权并查集、可撤销并查集

相关题目: CSG 1099(Kruskal内部), 1079


4.4 单调栈

适用场景: 找左右第一个更大/更小元素、柱状图最大矩形

// 找每个元素右边第一个比它大的元素下标
vector<int> next_greater(vector<int>& a) {
    int n = a.size();
    vector<int> res(n, -1);
    stack<int> st;
    for (int i = n - 1; i >= 0; i--) {
        while (!st.empty() && a[st.top()] <= a[i]) st.pop();
        if (!st.empty()) res[i] = st.top();
        st.push(i);
    }
    return res;
}

// 柱状图最大矩形面积
long long largest_rectangle(vector<int>& h) {
    int n = h.size();
    vector<int> left(n), right(n);
    stack<int> st;
    for (int i = 0; i < n; i++) {
        while (!st.empty() && h[st.top()] >= h[i]) st.pop();
        left[i] = st.empty() ? -1 : st.top();
        st.push(i);
    }
    while (!st.empty()) st.pop();
    for (int i = n - 1; i >= 0; i--) {
        while (!st.empty() && h[st.top()] >= h[i]) st.pop();
        right[i] = st.empty() ? n : st.top();
        st.push(i);
    }
    long long ans = 0;
    for (int i = 0; i < n; i++)
        ans = max(ans, 1LL * h[i] * (right[i] - left[i] - 1));
    return ans;
}

思路要点:

  • 维护栈内元素单调性(递增或递减)
  • 每个元素入栈出栈各一次,O(n)
  • 核心:弹出条件决定了"第一个不满足"的位置

相关题目: CSG 1069, 1094


📐 五、数学与数论

5.1 快速幂 / 逆元 ⚡

适用场景: 大指数取模、组合数预处理

long long pow_mod(long long a, long long b, long long mod) {
    long long res = 1;
    a %= mod;
    while (b) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

// 费马小定理求逆元(mod为质数)
long long inv(long long a, long long mod) {
    return pow_mod(a, mod - 2, mod);
}

思路要点:

  • 二进制拆分指数,逐位平方
  • 费马小定理:a^(p-1) ≡ 1 mod p → a^(p-2) ≡ a^(-1)
  • 时间复杂度 O(log b)

5.2 组合数预处理

适用场景: 频繁查询组合数 C(n,k) mod p

const int MOD = 1e9 + 7;
long long fact[N], inv_fact[N];

void init_combo(int n) {
    fact[0] = 1;
    for (int i = 1; i <= n; i++)
        fact[i] = fact[i-1] * i % MOD;
    inv_fact[n] = pow_mod(fact[n], MOD - 2, MOD);
    for (int i = n - 1; i >= 0; i--)
        inv_fact[i] = inv_fact[i+1] * (i+1) % MOD;
}

long long C(int n, int k) {
    if (k < 0 || k > n) return 0;
    return fact[n] * inv_fact[k] % MOD * inv_fact[n-k] % MOD;
}

思路要点:

  • 预处理阶乘和阶乘逆元
  • C(n,k) = n! / (k! * (n-k)!)
  • 单次查询 O(1)
  • n 很大 mod 很小时用卢卡斯定理

相关题目: CSG 1070, 1054, 1158


5.3 矩阵快速幂

适用场景: 线性递推加速、图上路径计数

const int MOD = 1e9 + 7;
struct Matrix {
    long long m[105][105];
    int n;
    Matrix(int size) : n(size) { memset(m, 0, sizeof m); }
    
    Matrix operator*(const Matrix& b) const {
        Matrix res(n);
        for (int i = 0; i < n; i++)
            for (int k = 0; k < n; k++)
                if (m[i][k])
                    for (int j = 0; j < n; j++)
                        res.m[i][j] = (res.m[i][j] + m[i][k] * b.m[k][j]) % MOD;
        return res;
    }
};

Matrix matrix_pow(Matrix a, long long k) {
    Matrix res(a.n);
    for (int i = 0; i < a.n; i++) res.m[i][i] = 1;  // 单位矩阵
    while (k) {
        if (k & 1) res = res * a;
        a = a * a;
        k >>= 1;
    }
    return res;
}

思路要点:

  • 矩阵乘法满足结合律,可用快速幂思想
  • 线性递推:构造转移矩阵,乘初始向量
  • 图上走k步路径数:邻接矩阵k次方
  • 时间复杂度 O(n³ log k)

相关题目: CSG 1076


5.4 最大公约数 / 扩展欧几里得

int gcd(int a, int b) {
    while (b) { a %= b; swap(a, b); }
    return a;
}

// 扩展欧几里得:求ax + by = gcd(a,b)的解
long long exgcd(long long a, long long b, long long& x, long long& y) {
    if (b == 0) { x = 1; y = 0; return a; }
    long long d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

六、计算几何

6.1 基础几何模板

const double eps = 1e-8;
const double PI = acos(-1.0);

int sign(double x) {
    if (fabs(x) < eps) return 0;
    return x < 0 ? -1 : 1;
}

struct Point {
    double x, y;
    Point(double x = 0, double y = 0) : x(x), y(y) {}
    Point operator+(Point b) { return Point(x + b.x, y + b.y); }
    Point operator-(Point b) { return Point(x - b.x, y - b.y); }
    Point operator*(double k) { return Point(x * k, y * k); }
    double operator*(Point b) { return x * b.x + y * b.y; }  // 点积
    double operator^(Point b) { return x * b.y - y * b.x; }  // 叉积
    double len() { return sqrt(x*x + y*y); }
    double len2() { return x*x + y*y; }
};

// 叉积:ab × ac
double cross(Point a, Point b, Point c) {
    return (b - a) ^ (c - a);
}

// 点积:ab · ac
double dot(Point a, Point b, Point c) {
    return (b - a) * (c - a);
}

// 两点距离
double dist(Point a, Point b) {
    return (a - b).len();
}

思路要点:

  • 点积:判断夹角(锐角/钝角/垂直)、投影长度
  • 叉积:判断转向(顺/逆时针)、平行四边形面积
  • 浮点比较用 sign 函数,避免精度问题

📏 6.2 点到线段距离 / 线段相交

// 点p到线段ab的距离
double point_to_seg(Point p, Point a, Point b) {
    if (sign(dot(a, b, p)) < 0) return dist(p, a);
    if (sign(dot(b, a, p)) < 0) return dist(p, b);
    return fabs(cross(a, b, p)) / dist(a, b);
}

// 判断线段相交(规范相交)
bool seg_intersect(Point a1, Point a2, Point b1, Point b2) {
    double c1 = cross(a1, a2, b1), c2 = cross(a1, a2, b2);
    double c3 = cross(b1, b2, a1), c4 = cross(b1, b2, a2);
    return sign(c1) * sign(c2) < 0 && sign(c3) * sign(c4) < 0;
}

📚 相关题目:

  • CSG 1049, 1098, 1123, 1143, 1114

6.3 凸包(Andrew 算法)

vector<Point> convex_hull(vector<Point> p) {
    int n = p.size(), k = 0;
    if (n <= 1) return p;
    sort(p.begin(), p.end(), [](Point a, Point b) {
        return sign(a.x - b.x) ? a.x < b.x : a.y < b.y;
    });
    vector<Point> res(n * 2);
    // 下凸壳
    for (int i = 0; i < n; res[k++] = p[i++])
        while (k >= 2 && sign(cross(res[k-2], res[k-1], p[i])) <= 0) k--;
    // 上凸壳
    for (int i = n - 2, t = k + 1; i >= 0; res[k++] = p[i--])
        while (k >= t && sign(cross(res[k-2], res[k-1], p[i])) <= 0) k--;
    res.resize(k - 1);
    return res;
}

思路要点:

  • 按 x 排序,x 相同按 y
  • 从左到右扫下凸壳,右到左扫上凸壳
  • 栈内维护凸性,不满足则弹出
  • 时间复杂度 O(n log n)

相关题目: CSG 1068


七、搜索与博弈

7.1 BFS 广度优先搜索

适用场景: 最短步数、最少操作、迷宫最短路

int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
int dist[N][N];

int bfs(int sx, int sy, int tx, int ty, int n, int m, vector<string>& g) {
    memset(dist, -1, sizeof dist);
    queue<pair<int, int>> q;
    dist[sx][sy] = 0;
    q.push({sx, sy});
    while (!q.empty()) {
        auto [x, y] = q.front(); q.pop();
        if (x == tx && y == ty) return dist[x][y];
        for (int i = 0; i < 4; i++) {
            int nx = x + dx[i], ny = y + dy[i];
            if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
            if (dist[nx][ny] != -1 || g[nx][ny] == '#') continue;
            dist[nx][ny] = dist[x][y] + 1;
            q.push({nx, ny});
        }
    }
    return -1;
}

思路要点:

  • 队列实现,层序遍历
  • 首次到达即为最短距离
  • 必须判重(visited / dist数组)
  • 时间复杂度 O(V + E)

相关题目: CSG 1037, 1038, 1065, 1131, 1195


7.2 DFS 回溯

适用场景: 枚举所有方案、排列组合、剪枝搜索

int ans = 0;
int path[N];

void dfs(int depth, int state) {
    if (depth == n) {
        ans++;  // 找到一个合法方案
        return;
    }
    for (int i = 0; i < n; i++) {
        if (check(state, i)) {  // 判断是否可选
            path[depth] = i;
            dfs(depth + 1, state | (1 << i));
            // 回溯(path会被覆盖,有时需要显式撤销)
        }
    }
}

思路要点:

  • 递归深入,到底后回溯
  • 剪枝:可行性剪枝、最优性剪枝
  • 状态压缩:用位运算加速判重

相关题目: CSG 1051, 1128, 1158


7.3 SG 函数 / Nim 博弈

适用场景: 公平组合游戏、多堆石子

int sg[N];
bool vis[N];

// 求单堆SG函数
void get_sg(int n, vector<int>& s) {  // s为可选操作集合
    sg[0] = 0;
    for (int i = 1; i <= n; i++) {
        memset(vis, 0, sizeof vis);
        for (int op : s) {
            if (i >= op) vis[sg[i - op]] = true;
        }
        for (int j = 0; ; j++)  // mex
            if (!vis[j]) { sg[i] = j; break; }
    }
}

// 多游戏总SG = 各堆SG异或和
// 总SG != 0 先手胜,= 0 后手胜

思路要点:

  • SG(x) = mex{ SG(y) | y是x的后继状态 }
  • mex:最小的非负整数
  • 多个独立游戏:总SG = 各SG异或
  • Nim游戏:每堆SG就是石子数

相关题目: CSG 1039, 1214


八、🤔 贪心

8.1 区间调度 / 不重叠区间最多

// 按右端点排序,依次选不重叠的
int max_non_overlap(vector<pair<int, int>>& intervals) {
    sort(intervals.begin(), intervals.end(),
         [](auto& a, auto& b) { return a.second < b.second; });
    int cnt = 0, last = -1e9;
    for (auto& [l, r] : intervals) {
        if (l >= last) {
            cnt++;
            last = r;
        }
    }
    return cnt;
}

相关题目: CSG 1090


8.2 田忌赛马

// 贪心策略:
// 1. 最慢赢最慢,赢一场
// 2. 最快赢最快,赢一场
// 3. 最慢碰最快,输一场(消耗对方最快)
int tianji_horse(vector<int>& a, vector<int>& b) {
    sort(a.begin(), a.end());
    sort(b.begin(), b.end());
    int la = 0, ra = a.size() - 1;
    int lb = 0, rb = b.size() - 1;
    int win = 0;
    while (la <= ra) {
        if (a[ra] > b[rb]) { win++; ra--; rb--; }       // 最快赢最快
        else if (a[la] > b[lb]) { win++; la++; lb++; }  // 最慢赢最慢
        else {
            if (a[la] < b[rb]) win--;                   // 最慢碰最快
            la++; rb--;
        }
    }
    return win;
}

相关题目: CSG 1104


九、二分与三分

9.1 二分答案

适用场景: 最大化最小值、最小化最大值(答案单调)

// 最大化最小值:找最大的mid使得check(mid)为真
int binary_search_max(int l, int r) {
    while (l < r) {
        int mid = (l + r + 1) >> 1;  // 上取整!
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

// 最小化最大值:找最小的mid使得check(mid)为真
int binary_search_min(int l, int r) {
    while (l < r) {
        int mid = (l + r) >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}

思路要点:

  • 答案具有单调性:可行→更大/更小都可行
  • check函数验证某个答案是否可行
  • 注意上下取整,避免死循环
  • 浮点数二分:固定次数或 r-l < eps

相关题目: CSG 1050, 1060, 1222


9.2 三分求极值

适用场景: 单峰函数求最大值/最小值

// 三分求最小值(凸函数)
double ternary_search(double l, double r) {
    for (int i = 0; i < 100; i++) {  // 迭代100次精度足够
        double m1 = l + (r - l) / 3;
        double m2 = r - (r - l) / 3;
        if (f(m1) < f(m2)) r = m2;
        else l = m1;
    }
    return f(l);
}

思路要点:

  • 单峰(凸)函数才能用
  • 取两个三等分点比较,缩小区间
  • 通常迭代100次精度足够
  • 整数三分注意边界

相关题目: CSG 1099, 1050


附录:题目知识点速查表

题号题目名称主要知识点
CSG 1035一二三模拟/字符串
CSG 1036报数游戏模拟/数学
CSG 1037多连块拼图搜索/模拟
CSG 1038多连块分解搜索/枚举
CSG 1039盒子游戏博弈/找规律
CSG 1040打怪升级DP/记忆化
CSG 1041最优对称路径搜索/DP
CSG 1042Pieces and Discs计算几何
CSG 1043Super Poker生成函数/背包
CSG 1044Super Poker II背包/数论
CSG 1045RMQ with Shifts线段树/RMQ
CSG 1046汽水瓶数学/模拟
CSG 1047弟弟的作业模拟
CSG 1048数字整除数论/数位
CSG 1049台球碰撞计算几何/模拟
CSG 1050内部收益率二分/数学
CSG 1051Biggest NumberDFS/贪心
CSG 1052Repairing a Road最短路
CSG 1053射击游戏DP/计数
CSG 1054战场的数目DP/组合数学
CSG 1055Infinite Dictionaries字典树/模拟
CSG 1056Tetrahedrons and Spheres三维几何
CSG 1057三家人思维/数学
CSG 1058机器人的指令模拟
CSG 1059Updating a Dictionary模拟/字符串
CSG 1060平方根大搜索二分/高精度
CSG 1061最短的名字Trie/字符串
CSG 1062Kingdoms图论/最小割
CSG 1063网格中的三角形计算几何/计数
CSG 1064Tin Cutter II计算几何
CSG 1065Collecting CoinsBFS/搜索
CSG 1066病毒字符串/LCS
CSG 1067Cross-Shaped Tests差分/贪心
CSG 1068安全区域三维凸包
CSG 1069全1子矩阵DP/单调栈
CSG 1070组合数组合数学
CSG 1071Distinct Substrings后缀自动机
CSG 1072Modulo Nine数位DP
CSG 1073Numbers字符串/枚举
CSG 10744 Buttons数学/构造
CSG 1075字典序排列/贪心
CSG 1076有向图矩阵快速幂
CSG 10772019树/异或
CSG 1078Parity of Tuples位运算/FWT
CSG 1079双向链表练习题链表/模拟
CSG 10802018计数/DP
CSG 1081时间旅行贪心/数学
CSG 1082卖萌表情模拟/字符串
CSG 1083Grid图论/计数
CSG 1084Fixed Point置换/数学
CSG 1085排列贪心/构造
CSG 1086千万别用树套树线段树/树状数组
CSG 1087Steiner Tree斯坦纳树/状压DP
CSG 1088买一送一树/统计
CSG 1089Use FFTFFT/多项式
CSG 10902020贪心/字符串
CSG 10912020 vs 2018模拟/字符识别
CSG 1092Absolute Difference Equation数学/找规律
CSG 1093String Commutativity字符串/数论
CSG 1094Make Rounddog Very Happy单调栈/二分
CSG 1095抄板子NTT/多点求值
CSG 1096奇矩阵构造/线性代数
CSG 1097矩形并计算几何
CSG 1098共线点计算几何
CSG 1099Minimum Spanning TreeMST/三分
CSG 1100Spanning Trees生成树计数
CSG 1101Seating Arrangement贪心/DP
CSG 1102Simplified Blackjack概率DP
CSG 1103Taking Photo贪心/排序
CSG 1104Tian Ji’s Horse Race Again贪心
CSG 1105Directed Chain Decomposition树/DP
CSG 1106Forgot Password图论/推理
CSG 1108Finding Words字符串/枚举
CSG 1109Nearest Maintenance Point多源最短路
CSG 1110Monitoring Target计算几何
CSG 1111Football Training Camp模拟/数学
CSG 1112Aerial Tramway计算几何
CSG 1113大还是小?模拟/字符串
CSG 1114多边形的公共部分计算几何
CSG 1115错误的算法思维/构造
CSG 1116简单的图论问题?最短路/Dijkstra
CSG 1117阶乘除法数论/因数分解
CSG 1118Graph Guessing图论/概率
CSG 1119聊天止于呵呵字符串/模拟
CSG 1120Internet of Lights and Switches位运算/哈希
CSG 1121又一道简单题枚举/数学
CSG 1122Keep Fit!DP/前缀和
CSG 1123点到圆弧的距离计算几何
CSG 1124积木玩具三维/计算几何
CSG 1125酷酷的单词字符串/模拟
CSG 1127超大型LED显示屏模拟
CSG 1128地图的四着色DFS/计数
CSG 1129Giving directions to the tree树/拓扑
CSG 1130Happy Robot模拟
CSG 1131残缺的棋盘BFS/数学
CSG 1132Just another pachinko-like machine概率/DP
CSG 1133Kick the ball!概率/模拟
CSG 11342016数论/容斥
CSG 1136Three Capitals图论/数学
CSG 1137Toll分层图最短路
CSG 1138最长上升子序列DP/计数
CSG 1139地铁最短路
CSG 1140Parenthesis括号/前缀和
CSG 1141Reverse数学/数位
CSG 1142Tree Intersection树/启发式合并
CSG 1143三角形和矩形计算几何
CSG 1144盖房子DP/计数
CSG 115712强赛模拟
CSG 1158方格填数组合/杨氏矩阵
CSG 1159Average of Two Numbers双指针/枚举
CSG 1160Sum Them数学/公式
CSG 1161Not the Only TreeBST/计数
CSG 1162最大的数贪心/交换
CSG 1163洞穴探宝背包/搜索
CSG 1164Circle Intersection三维几何
CSG 1165戴森球计划计算几何/积分
CSG 1166String Set字符串/Trie
CSG 1167双排积木DP/计数
CSG 1189一起坐火车网络流
CSG 1190Complex Maze计算几何/最短路
CSG 1191Phalanx Dance差分/数据结构
CSG 1192Big Matrix数学/矩阵
CSG 1193走,做核酸去!最短路/时间依赖
CSG 1194黑白跳棋博弈/BFS
CSG 1195华容道BFS/状态搜索
CSG 1197共现的数数据结构/统计
CSG 1198Fibonacci Cane数学/斐波那契
CSG 1199Substrings Same as PrefixKMP/前缀函数
CSG 1213开开心心233模拟/二分
CSG 1214square game博弈/SG函数
CSG 1215室温超导字符串/KMP
CSG 1216Container Orders贪心/数论
CSG 1217ytree数据结构/树
CSG 1218宝石交易图论/最短路
CSG 1219套娃收纳贪心/DP
CSG 1221hard math数学/数论
CSG 1222radius三维几何/二分
CSG 1223毕业季图论/DP
CSG 1321贪吃蛇模拟/贪心
CSG 1322HolyK’s Land树/图论
CSG 1323easy math数学/对数
CSG 1324Too much noise!分类/曼哈顿距离
CSG 1325拼接串字符串/双指针
CSG 1326阅读理解DFS/树性质
CSG 1328经文字符串/KMP
CSG 1329数据检索系统哈希/布隆过滤器
CSG 1330Beautiful Sequence滑动窗口/数学
CSG 1331渡劫概率/期望DP

使用建议:

  1. 每个知识点先理解思路,再背模板代码
  2. 模板要能默写,比赛时直接套用修改
  3. 做完对应题号的题目巩固,注意变式题的坑点
  4. 重点掌握:Dijkstra、线段树、DP各类、KMP、并查集、快速幂、二分答案 —— 这些是最高频考点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值