#7375. 2026/6/14/周日下午小组笔记(dij堆优化)

2026/6/14/周日下午小组笔记(dij堆优化)

📚 编程复习笔记

2026年6月12日 — 6月16日


💡 阅读指南:每个知识点按 题型归类 → 核心思路 → 易错提醒 排列,重点标注 ⚠️ 和口诀 🎯 帮助快速回忆。


🔵 专题一:最短路算法(本周核心)

这是最重要的专题,务必彻底掌握!

📌 1.1 Dijkstra 算法 — 单源最短路(正权边)

涉及题目:P233 最短路径 I、P619 【模板】最短路-Dij堆优化、P1496 最短路计数

🎯 核心口诀:「贪心取最小,松弛更新邻」

算法步骤:
1. 初始化 dist[起点] = 0,其余 = ∞
2. 每次取出未访问中 dist 最小的点 u
3. 用 u 去松弛所有邻居 v:if dist[u] + w < dist[v] → 更新
4. 标记 u 已访问,重复直到所有点处理完

⚠️ 易错点

错误 正确做法
用普通数组找最小值 → O(n²) 超时 优先队列(小根堆) → O((n+m)logn)
处理重边时只存一条 所有边,算法自动选最短
自环边直接忽略 正权自环不影响结果,可以不管
忘记 long long 边权可达 10⁹,路径可达 10¹⁴,必须 long long
堆中过期数据不跳过 if (d > dist[u]) continue; 跳过旧记录

📝 Dij 堆优化模板

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,int> pli;
const ll INF = 1e18;

int main() {
    int N, M; cin >> N >> M;
    vector<vector<pair<int,int>>> g(N+1);
    for (int i = 0; i < M; i++) {
        int a, b, c; cin >> a >> b >> c;
        g[a].push_back({b, c});
        g[b].push_back({a, c}); // 无向图
    }
    vector<ll> dist(N+1, INF);
    priority_queue<pli, vector<pli>, greater<pli>> pq;
    dist[1] = 0; pq.push({0, 1});
    while (!pq.empty()) {
        auto [d, u] = pq.top(); pq.pop();
        if (d > dist[u]) continue; // 过期数据跳过
        for (auto [v, w] : g[u]) {
            if (dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
                pq.push({dist[v], v});
            }
        }
    }
    cout << dist[N] << endl;
}

📌 1.2 最短路计数

涉及题目:P1496 最短路计数

🎯 核心思路:在 BFS/Dij 过程中,顺便统计路径数

关键逻辑:
- if dist[u] + w < dist[v]:  cnt[v] = cnt[u]    // 发现更短路,重置计数
- if dist[u] + w == dist[v]: cnt[v] += cnt[u]   // 等长路径,累加计数

⚠️ 易错点

  • 无权图用 BFS,有权图用 Dij
  • 结果对 100003 取模,但比较距离时不要取模
  • 重边会产生多条等长路径,不要去重

📌 1.3 最短距离和路径问题

涉及题目:P2530 最短距离和路径问题

🎯 核心思路:Dij + 记录前驱,路径字典序最小

记录路径方法:
1. 用 pre[] 数组记录每个点的前驱
2. 字典序最小:松弛时,如果 dist 相同,比较路径字典序
3. 技巧:按编号从小到大遍历邻居即可

⚠️ 本题卡点

  • TLE 可能是输出路径时用了递归,大数据栈溢出 → 用迭代回溯
  • WA 可能是字典序处理不对 → 同距离时选编号更小的前驱
  • 多条边要全部存储,不能只保留最短的

🟢 专题二:贪心算法

📌 2.1 合并果子(哈夫曼编码思想)

涉及题目:P2236 【基础】合并果子

🎯 核心口诀:「每次合并最小的两堆」

算法:用小根堆(优先队列)
1. 所有果子重量放入小根堆
2. 每次取出最小的两个 a, b
3. 合并耗费 a+b,把 a+b 放回堆中
4. 累加耗费,重复直到堆中只剩一堆
priority_queue<int, vector<int>, greater<int>> pq;
// 读入所有重量 push 进 pq
int total = 0;
while (pq.size() > 1) {
    int a = pq.top(); pq.pop();
    int b = pq.top(); pq.pop();
    total += a + b;
    pq.push(a + b);
}
cout << total << endl;

💡 为什么这样最优? 贪心证明:每次让最小的先合并,避免大的数被重复计算。


🟡 专题三:思维/构造题

📌 3.1 你的名字 — 字符串重排判定

涉及题目:P7220 你的名字

🎯 核心思路:判断两个字符串是否为字母异位词

只需统计两个字符串中每个字母的出现次数是否完全相同。
排序后比较 / 计数数组比较 都可以。
sort(s.begin(), s.end());
sort(t.begin(), t.end());
cout << (s == t ? "YES" : "NO") << endl;

📌 3.2 平移 MEX

涉及题目:P7252 平移 MEX

🎯 核心思路:选择偏移量 x,使得 MEX 最大

MEX 定义:数组中没出现的最小非负整数
关键观察:平移 x 后,数组变成 {a₁+x, a₂+x, ..., aₙ+x}
我们要选 x,使得 0,1,2,...,k-1 都出现,而 k 不出现

⚠️ 易错点

  • x 可以为负数!不要只考虑 x ≥ 0
  • 枚举可能的 MEX 值 k,检查是否存在 x 使 0~k-1 都出现
  • 时间复杂度 O(n²),n ≤ 3000 可以接受

📌 3.3 内存溢出错误

涉及题目:P7253 内存溢出错误

🎯 核心思路:模拟操作,遇到溢出则重置

1. 逐个执行操作
2. 每次操作后检查是否越界(a[bᵢ] > h)
3. 如果越界 → 重置数组为初始值
4. 继续执行后续操作(不会回退到之前已执行的操作)

⚠️ 易错点

  • 重置后继续执行后面的操作,不是从头开始!
  • 注意是 a[bᵢ] 越界,不是 cᵢ 越界

📌 3.4 兑换硬币

涉及题目:P1651 【入门】兑换硬币

🎯 核心思路:枚举 + 数学

1元 = 100分,每种至少1枚
a + 2b + 5c = 100, a,b,c ≥ 1
→ (a-1) + 2(b-1) + 5(c-1) = 100 - 1 - 2 - 5 = 92
设 a'=a-1, b'=b-1, c'=c-1,则 a' + 2b' + 5c' = 92, a',b',c' ≥ 0
枚举 c' 从 0 到 18,对每个 c' 枚举 b' 即可

🔴 高频错误模式总结

错误类型 典型表现 解决方法
算法选择错误 最短路用 Floyd 超时 10⁵ 级别用 Dij 堆优化
数据类型溢出 用 int 存路径长度 边权 10⁹ × 10⁵ → 必须 long long
重复提交不改思路 同一题连续 TLE/WA 换思路,不要只改细节
边界条件遗漏 重边、自环、空图 写代码前先想边界

📋 复习检查清单

  • [ ] 能手写 Dijkstra 堆优化版本
  • [ ] 理解最短路计数的转移方程
  • [ ] 知道路径记录和字典序最小怎么处理
  • [ ] 掌握哈夫曼/合并果子的贪心策略
  • [ ] 理解 MEX 的定义和构造方法
  • [ ] 模拟题注意"重置后继续"的逻辑
  • [ ] 所有最短路题目检查是否需要 long long