AIGC标识 搜索

搜索算法讲义

搜索是"有组织的枚举":正解时它是完整算法,非正解时它是最可靠的部分分。本讲系统讲 DFS / BFS 的变体、剪枝的六种手法、迭代加深与 IDA、双向搜索与折半枚举、A、舞蹈链、随机化搜索,以及搜索复杂度的估计方法。
建议课时:4 课时。前置:递归、位运算、基本 DP、哈希表。


0. 定位

为什么要学搜索:

  1. 暴力。
  2. 很多"正解"本身就是搜索加剪枝(数独、小木棍、斗地主)。
  3. 折半枚举、IDA*、DLX 等是省选级别的正解工具。
  4. 搜索写得快、对拍暴力也靠它。

搜索题的四个要素:状态是什么、从一个状态能到哪些状态(转移)、初始状态与目标、如何避免重复(判重 / 剪枝)。设计搜索先把这四个写清楚。


1. 基本框架

1.1 DFS 与回溯

void dfs(int step, State& s) {
    if (finished(s)) { record(s); return; }
    for (auto choice : options(s)) {
        if (!feasible(s, choice)) continue;     // 可行性剪枝
        apply(s, choice);
        dfs(step + 1, s);
        undo(s, choice);                        // 回溯
    }
}

"做选择、递归、撤销选择"是回溯的三段式。用引用传状态 + 撤销,比每层复制状态快得多。

1.2 BFS

逐层扩展,第一次到达某状态时的步数就是最短步数(边权全为 1)。需要判重表。

1.3 记忆化搜索

状态的答案只依赖状态本身时,存下来。记忆化搜索 = 隐式图上的 DP,适合拓扑序不明显(如状态由多个不规则变量决定)或只需要访问部分状态的场合。

1.4 选择哪种

情况 选择
求最少步数、边权 1 BFS
求所有解、深度有限 DFS
深度很大但答案很浅 IDDFS / 双向 BFS
状态空间大,有好的估价 IDA* / A*
状态可枚举且有重叠子问题 记忆化
枚举 \(n \le 40\) 的子集 折半

2. 剪枝六法

剪枝是搜索的核心。分六类,遇到题按顺序检查每一类能不能用。

2.1 可行性剪枝

当前状态已经不可能到达合法解,立即返回。尽早判断:能在选择时判断就不要在递归后判断。

例:拼木棍时剩余长度不够;数独填数时行列宫已有该数。

2.2 最优性剪枝

当前代价 + 剩余最少可能代价 \(\ge\) 已知最优解,返回。"剩余最少可能代价"是一个下界估计,估计越紧剪枝越强。这是 IDA* 的雏形。

例:TSP 中当前路径长 + 剩余点数 × 最小边权 \(\ge\) 最优。

2.3 排除等效冗余

多条搜索分支会到达本质相同的状态时只走一条。

  • 组合而非排列:选集合时强制下标递增。
  • 对称性:N 皇后第一行只枚举左半;正方体染色固定一个面。
  • 相同元素:多个相同物品,只尝试第一个;同一层内,与上一次失败的选择相同的值不再尝试。

2.4 优化搜索顺序

先搜"更可能成功"或"约束更强"的分支:

  • 从大到小放物品(大的更难放,早失败早剪枝)。
  • 数独 / 约束满足:每次选候选值最少的格子(MRV 启发)。
  • 求最优解时,先搜更优的方向,让最优性剪枝尽早生效。

2.5 记忆化

见 1.3。当状态能压缩(位运算、哈希)且重复出现时。

2.6 上界 / 下界估计

结合 2.2,预处理"从每个状态出发的最优可能",如"剩余物品的价值总和"(背包)、"剩余层数的最小体积"(生日蛋糕)。

2.7 例题:[NOIP1997] 小木棍(洛谷 P1120)

\(n \le 65\) 根木棍拼成若干等长原木棍,求最小原长。

剪枝清单(每一条都对应上面一类):

  1. 原长在 \([\max a_i,\ \sum a_i / 2]\) 内枚举,且必须整除 \(\sum a_i\)(可行性)。
  2. 木棍从大到小排序(搜索顺序)。
  3. 拼当前原木棍时,从上一根用的位置之后开始(排除冗余:组合)。
  4. 尝试失败的长度,跳过后面所有相同长度(排除冗余)。
  5. 若当前原木棍是空的(刚开始拼)就失败,或者恰好拼满时失败,直接回溯到上一根(关键剪枝:这两种情况下换别的木棍也不可能成功,因为当前这根木棍总得被放在某根原木棍里,而现在的位置是等价的)。
bool dfs(int cnt, int cur, int last) {          // 已拼 cnt 根,当前拼到 cur,从 last 开始试
    if (cnt == total) return true;
    if (cur == len) return dfs(cnt + 1, 0, 1);
    int fail = 0;
    for (int i = last; i <= n; i++) {
        if (used[i] || cur + a[i] > len || a[i] == fail) continue;
        used[i] = true;
        if (dfs(cnt, cur + a[i], i + 1)) return true;
        used[i] = false;
        fail = a[i];
        if (cur == 0 || cur + a[i] == len) return false;   // 剪枝 5
    }
    return false;
}

2.8 例题:生日蛋糕(洛谷 P1731)

\(m\) 层蛋糕,体积 \(n\),最小化表面积。剪枝:预处理最小体积 / 最小面积前缀;当前体积 + 剩余最小体积 \(> n\) 剪;当前面积 + 剩余最小面积 \(\ge\) 最优剪;剩余体积 \(V\) 能贡献的面积下界 \(\ge 2V / R\)(因为每层侧面积 \(2rh \ge 2rh\cdot r/R\),\(V = \sum r^2h\))。最后一条是"数学推出的紧下界",是这题的精髓。


3. 迭代加深与 IDA*

3.1 迭代加深(IDDFS)

限制 DFS 深度 \(d = 1, 2, 3, \dots\),每次从头搜。适合:答案深度小但搜索树分支大、状态无法判重(BFS 存不下)。

代价:重复搜索浅层。但分支因子 \(b\) 时,总代价 \(\sum_{i\le d}b^i = O(b^d)\),与最后一次同阶。

例:埃及分数(洛谷 P1763)。把 \(\frac{a}{b}\) 写成最少的不同单位分数之和。迭代加深枚举项数,第 \(k\) 项分母 \(x\) 的范围:$x > $ 上一项分母,且 \(\frac{\text{剩余项数}}{x} \ge \frac{a}{b}\)(否则剩下的加起来不够),即 \(x \le \frac{\text{剩余项数}\cdot b}{a}\)。

3.2 IDA*

迭代加深 + 估价函数 \(h\):当前深度 \(g\) + 估价 $h > $ 深度限制时剪枝。要求 \(h \le\) 真实剩余步数(可采纳),否则可能剪掉最优解。

bool dfs(int g, int limit, State& s) {
    if (g + h(s) > limit) return false;
    if (isGoal(s)) return true;
    for (auto& nxt : moves(s)) { apply; if (dfs(g + 1, limit, s)) return true; undo; }
    return false;
}
for (int limit = h(start);; limit++) if (dfs(0, limit, start)) { answer = limit; break; }

估价函数的设计:把问题松弛(去掉某些约束),松弛后的最优解就是可采纳的 \(h\)。

问题 估价
15 数码 每块到目标的曼哈顿距离之和
骑士精神(P2324,棋子交换) 不在目标位置的棋子数(每步至多修正一个)
排书 Booksort(POJ 3460) \(\lceil\text{错误后继数}/3\rceil\)(每次移动至多改 3 个后继关系)
魔板 / 魔方 错位块数 / 8 等

3.3 A*

BFS 版本的估价搜索:优先队列按 \(f = g + h\) 出队。

  • \(h\) 可采纳 \(\Rightarrow\) 目标第一次出队时是最优。
  • \(h\) 还满足一致性(\(h(u) \le w(u,v) + h(v)\))\(\Rightarrow\) 每个状态第一次出队即最优,可以像 Dijkstra 一样标记已访问。否则同一状态可能多次出队,要允许重复。
  • 第 \(k\) 短路:\(h\) = 到终点的最短路(反图 Dijkstra),终点第 \(k\) 次出队即第 \(k\) 短路。每个点最多出队 \(k\) 次。

A vs IDA:A 需要存所有状态(内存),IDA 不存但重复搜。状态能存下用 A,否则 IDA。


4. BFS 变体

4.1 双向 BFS

从起点和终点同时扩展,交替扩展较小的那一层,相遇即得答案。分支 \(b\)、深度 \(d\) 时状态数从 \(b^d\) 降到 \(2b^{d/2}\)。

条件:终点明确、转移可逆(或能反向枚举前驱)。

[NOIP2002] 字串变换(P1032):规则 \(\le 6\),步数 \(\le 10\),正向 \(6^{10}\) 太大,双向各 5 步。

4.2 0-1 BFS

边权只有 0 和 1:用双端队列,0 边的后继放队首,1 边放队尾。\(O(V + E)\),比 Dijkstra 少一个 \(\log\)。

4.3 优先队列 BFS

= Dijkstra。状态搜索里带权转移时用。

4.4 多源 / 分层 BFS

多个起点同时入队(多源最短距离);状态带一维"层"(如"已用的传送次数"、"携带的钥匙集合"),[NOI 网络流 24 题] 孤岛营救问题(P4011):状态 \((x, y, \text{钥匙集合})\)。

4.5 判重

状态 判重方式
小整数 / 坐标 数组
排列(如八数码) 康托展开 \(O(n^2)\) 映射到 \([0, n!)\)
集合 状压位掩码
棋盘 / 大结构 哈希(Zobrist:每个位置每种取值一个随机数,异或)
任意 unordered_set<string> 或自定义哈希

康托展开:

ll cantor(int* a, int n) {                    // 排列 → 排名
    ll r = 0;
    for (int i = 0; i < n; i++) {
        int c = 0;
        for (int j = i + 1; j < n; j++) if (a[j] < a[i]) c++;
        r += c * fac[n - 1 - i];
    }
    return r;
}

逆康托展开:依次用 \(r / \text{fac}[n-1-i]\) 确定第 \(i\) 位是"剩余数中第几小"。

八数码(P1379):\(9! = 362880\) 个状态,双向 BFS + 康托,或 A*(曼哈顿)。奇偶性判可达:把 0 去掉后逆序对奇偶性不变(\(3\times3\) 时)。


5. 折半枚举(Meet in the Middle)

5.1 思想

\(n \le 40\) 的子集枚举 \(2^{40}\) 不可行。把元素分两半各 \(2^{20}\),分别枚举出所有可能的"和",然后用排序 + 双指针 / 二分 / 哈希把两半配对。\(O(2^{n/2}\cdot n)\)。

5.2 例题:[CEOI2015] 世界冰球锦标赛(P4799)

\(n \le 40\) 场比赛各有票价,预算 \(M\),求可行的观看子集数。

vector<ll> A, B;
dfsHalf(0, n / 2, 0, A); dfsHalf(n / 2, n, 0, B);      // 各自所有子集和
sort(B.begin(), B.end());
ll ans = 0;
for (ll x : A) ans += upper_bound(B.begin(), B.end(), M - x) - B.begin();

5.3 例题:送礼物(AcWing 171 / 原题 NOI 导刊)

\(n \le 46\) 个物品,重量上限 \(W \le 2^{31}\),求能装的最大重量。前半枚举所有和并排序去重,后半枚举时二分找最大的 \(\le W - \text{sum}\)。优化:让前半(要存并排序的那半)稍小(如 \(n/2 + 2\) 放后半),减少排序量。大的物品放前半,让前半的和分布更稀疏。

5.4 例题:[USACO12OPEN] Balanced Cow Subsets(P3067)

\(n \le 20\),选非空子集分成两组和相等,求选法数。每个元素三种状态(不选 / 组 A / 组 B),\(3^{20}\) 太大,折半 \(3^{10}\),两半按"A 和 − B 和"配对,再用位掩码去重。

5.5 折半 + 状态

折半不只用于"求和"。任何"两半独立、合并条件可以排序 / 哈希检查"的枚举都可以折半:4 个数组各取一个和为 0(\(n^2\) 对 \(n^2\))、两段路径拼接、密码前后半。

5.6 双向搜索的一般形式

从初态和终态各搜一半深度,在中间用哈希表相遇。这是折半在"状态图"上的版本,4.1 的双向 BFS 是特例。


6. 状态设计与位运算

6.1 状压

集合用整数位掩码;棋盘每行一个掩码。转移用位运算一次处理一行。

  • 枚举子集:for (int T = S; T; T = (T - 1) & S)。
  • 枚举所有集合的所有子集:总 \(3^n\)。
  • lowbit:S & -S。
  • 枚举大小为 \(k\) 的子集(Gosper's hack):t = S & -S; r = S + t; S = (((r ^ S) >> 2) / t) | r。

6.2 位运算 N 皇后

void dfs(int row, int cols, int ld, int rd) {   // 三个掩码:列、左斜、右斜
    if (row == n) { ans++; return; }
    int avail = full & ~(cols | ld | rd);
    while (avail) {
        int p = avail & -avail; avail -= p;
        dfs(row + 1, cols | p, (ld | p) << 1, (rd | p) >> 1);
    }
}

\(n = 14\) 毫秒级。

6.3 "枚举最小未处理元素"技巧

覆盖类状压 DP / 搜索中,转移只考虑编号最小的未覆盖元素必须被哪个集合覆盖,避免同一状态从多个顺序到达。

[NOIP2016] 愤怒的小鸟(P2831):预处理任意两猪确定的抛物线覆盖集合 \(\text{cov}[i][j]\)。\(f[S]\) 从 \(S\) 中最低的未覆盖猪 \(i\) 出发,枚举 \(j\) 或单独打 \(i\)。\(O(2^n n)\)。

6.4 Zobrist 哈希

每个 (位置, 值) 对一个 64 位随机数,状态哈希 = 所有对的异或。修改一个位置只需两次异或,\(O(1)\) 增量更新。用于棋盘类判重。


7. 舞蹈链(DLX)

7.1 精确覆盖

\(0/1\) 矩阵,选若干行使每列恰有一个 1。数独、N 皇后、拼图都可建模成精确覆盖。

数独建模:\(729\) 行(格 × 数字),\(324\) 列:每格填一个数(81)、每行每数字一次(81)、每列每数字一次(81)、每宫每数字一次(81)。

7.2 十字链表与 Knuth's Algorithm X

每个 1 是一个节点,四向链接。选一列 \(c\)(元素最少的列);枚举该列每个节点所在行 \(r\);删除 \(r\) 覆盖的所有列(及这些列上的所有行);递归;恢复。

int L[M], R[M], U[M], D[M], col[M], row[M], sz[M], head[N], ans[N], tot;
void init(int c) {
    for (int i = 0; i <= c; i++) L[i] = i - 1, R[i] = i + 1, U[i] = D[i] = i, sz[i] = 0;
    L[0] = c; R[c] = 0; tot = c; memset(head, 0, sizeof head);
}
void insert(int r, int c) {
    ++tot; col[tot] = c; row[tot] = r; sz[c]++;
    U[tot] = U[c]; D[tot] = c; D[U[c]] = tot; U[c] = tot;
    if (!head[r]) head[r] = L[tot] = R[tot] = tot;
    else { L[tot] = L[head[r]]; R[tot] = head[r]; R[L[head[r]]] = tot; L[head[r]] = tot; }
}
void remove(int c) {
    L[R[c]] = L[c]; R[L[c]] = R[c];
    for (int i = D[c]; i != c; i = D[i])
        for (int j = R[i]; j != i; j = R[j]) U[D[j]] = U[j], D[U[j]] = D[j], sz[col[j]]--;
}
void resume(int c) {
    for (int i = U[c]; i != c; i = U[i])
        for (int j = L[i]; j != i; j = L[j]) U[D[j]] = D[U[j]] = j, sz[col[j]]++;
    L[R[c]] = R[L[c]] = c;
}
bool dance(int dep) {
    if (R[0] == 0) return true;
    int c = R[0];
    for (int i = R[0]; i; i = R[i]) if (sz[i] < sz[c]) c = i;
    remove(c);
    for (int i = D[c]; i != c; i = D[i]) {
        ans[dep] = row[i];
        for (int j = R[i]; j != i; j = R[j]) remove(col[j]);
        if (dance(dep + 1)) return true;
        for (int j = L[i]; j != i; j = L[j]) resume(col[j]);
    }
    resume(c);
    return false;
}

恢复必须与删除严格逆序。模板:洛谷 P4929。

7.3 重复覆盖

每列至少一个 1(如 N 皇后的斜线列、[HNOI2006] 最短母串的字符集覆盖)。remove 只删该列本身(不删其他行),配合 IDA* 的估价(至少还需几行:贪心数"互不相交的未覆盖列")。

7.4 数独的另一种写法

位运算 + MRV:每行、列、宫一个 9 位掩码,每次选候选最少的格子。对于普通数独比 DLX 更好写、速度相当。[NOIP2009] 靶形数独(P1074)求最优解,需要搜完所有解,MRV + 位运算是标准解。


8. 随机化与局部搜索

8.1 模拟退火

double T = 1e4, dec = 0.985, eps = 1e-3;
State cur = init(); double curV = f(cur); best = curV;
while (T > eps) {
    State nxt = perturb(cur, T);                 // 扰动幅度与 T 成正比
    double nv = f(nxt), d = nv - curV;
    if (d < 0 || exp(-d / T) > rand01()) cur = nxt, curV = nv;   // 最小化
    best = min(best, curV);
    T *= dec;
}
// 卡时:while ((double)clock() / CLOCKS_PER_SEC < 0.8) SA();

调参:初温取解的量级,降温 \(0.97 \sim 0.999\),多次重启。接受更差解的概率 \(e^{-\Delta/T}\) 是退火与爬山的唯一区别。

适用:解空间连续或"邻域"自然(交换两个元素、微调坐标)、目标函数容易计算、正解难写或不存在。

例题:P1337 [JSOI2004] 平衡点(物理上是势能最小,退火求坐标)、P2210 Haywire(排列扰动 = 交换)、P3878 [TJOI2010] 分金币(子集扰动 = 交换两组各一个)、P3959 [NOIP2017] 宝藏(随机化搜索也能过)。

8.2 爬山与随机重启

只接受更优解,快但易陷局部最优。随机多次初始化取最优。

8.3 随机化搜索顺序

DFS 时随机打乱分支顺序 + 卡时。对"存在性"问题(找一个解)常常极快,因为解分布在搜索树各处,随机顺序避免了系统性地先搜空分支。

8.4 随机化贪心

多次随机扰动输入顺序后贪心,取最优。例:随机顺序 Kruskal 求某些近似 MST 变体。

注意:这些方法不保证正确,比赛中作为"没有正解时的高分方案"或"验证正解的对拍工具"。


9. 搜索复杂度的估计与工程

9.1 Knuth 估计法

随机走一条从根到叶的路径,在深度 \(i\) 处记录分支数 \(b_i\),估计搜索树大小为 \(1 + b_1 + b_1b_2 + b_1b_2b_3 + \cdots\)。多次取平均。赛场上用它判断"暴搜能不能过"。

9.2 常见工程优化

  • 状态用引用 + 撤销,不复制。
  • 递归改手写栈(很少需要)。
  • 位运算替代数组扫描(候选集合、行列占用)。
  • 预处理转移表(如八数码的 0 的移动方向)。
  • 剪枝判断放在循环里而不是递归入口(少一层函数调用)。
  • inline、读入优化对搜索题帮助有限,剪枝才是关键。

9.3 搜索与 DP 的混合

  • 记忆化搜索处理不规则拓扑(如状态是一个集合 + 一个位置)。
  • 搜索枚举"结构"(如划分方案),每种结构内用 DP 算最优。
  • DP 转移中嵌入搜索(转移候选很多但大部分可以剪枝)。

10. 例题精讲

10.1 [NOIP2015] 斗地主(P2668)

思路:顺子(单顺、双顺、三顺)的选法影响最大,其余牌型(炸弹、三带、对子、单牌)可以贪心一次性算出。所以搜索只枚举顺子,每次搜索到叶子时用贪心计算剩余牌的最少出牌次数。剪枝:当前次数 \(\ge\) 最优。

教训:搜索的分支只放"真正需要决策"的部分,其余交给贪心 / DP。

10.2 [NOIP2004] 虫食算(P1092)

\(n\) 个字母代表 \(0..n-1\),加法竖式。搜索顺序:从最低位开始、按字母在竖式中出现的顺序(先出现的先赋值),因为低位确定后立刻可以检验。可行性剪枝:每列若三个字母都已赋值,检查进位是否一致;若部分赋值,检查是否可能(\(a + b\) 与 \(c\) 的模 \(n\) 关系允许进位 0 或 1)。

10.3 [NOIP2011] Mayan 游戏(P1312)

5 步内消除。剪枝:只向右移动(向左移动等价于左边的方块向右移,排除冗余);某种颜色的方块少于 3 个则无解;剩余方块数 $> $ 剩余步数 × 上限则剪。模拟消除的实现要小心。

10.4 骑士精神(P2324)

\(5\times5\) 棋盘,15 步内把 12 个骑士和空位复原。IDA*,\(h\) = 不在目标位置的骑士数。每层剪枝 \(g + h > \text{limit}\)。

10.5 [SCOI2005] 骑士精神 的近亲:[AHOI2012] 铁盘整理(P2534)

排序若干盘子,每次翻转前缀。\(h\) = 相邻不连续(差不为 1)的对数(每次翻转最多改变一对)。IDA*。

10.6 靶形数独(P1074)

位运算三掩码 + MRV + 搜完所有解取最大分。从约束最多的行开始(预先按空格数排序也行)。

10.7 [NOIP2017] 宝藏(P3959)

\(n \le 12\),正解状压 DP \(O(3^n n)\) 或 \(O(2^n n^2)\) 按层扩展;随机化搜索(随机顺序 + 卡时)也能过,是"随机化搜索作为兜底"的典型。


11. 常见错误清单

  1. 回溯忘记撤销。 尤其是多个状态变量时漏一个。
  2. 可行性剪枝放太晚。 在递归入口判断不如在枚举选择时判断。
  3. IDA 的估价不可采纳。* \(h\) 偏大会剪掉最优解,得到错误答案。
  4. A 用不一致的 \(h\) 却标记了已访问。* 会漏掉更优路径。
  5. 双向 BFS 每次都扩展整层且扩展大的一侧。 应扩展小的一侧,且相遇判定要在扩展时检查另一侧的访问表。
  6. 折半后合并时忘记去重或忘记空集。 子集和中空集是合法的(0),组合数要算上。
  7. DLX 恢复顺序错误。 必须与删除严格相反(列的逆序、行的逆序)。
  8. 康托展开数组下标从 1 起却按 0 计算。
  9. 模拟退火没有卡时或初温太低。 初温要与目标函数量级相当。
  10. 记忆化搜索的状态漏了一维。 导致不同状态共用答案。
  11. 小木棍剪枝 5 写错。 是"当前空 或 恰好填满 时失败即返回",不是"任何失败都返回"。
  12. N 皇后位运算掩码不截断。 左移后超出 \(n\) 位要 & full。

12. 课堂练习

  1. 用 Knuth 估计法估算 8 皇后无剪枝搜索树的大小(随机走 5 次取平均)。
  2. 为"15 数码"设计两个可采纳估价函数并比较强弱。(答:错位数 \(\le\) 曼哈顿和,后者更强)
  3. 证明双向 BFS 相遇时得到的是最短路。
  4. \(n = 30\),求子集和恰为 \(S\) 的方案数。写出折半的代码骨架。
  5. 用位运算写出 N 皇后,并在 \(n = 13\) 下计时。
  6. 把 \(4\times4\) 数独建模为精确覆盖,给出行数和列数。(答:\(64\) 行,\(64\) 列)
  7. 写出八数码可达性判定(逆序对奇偶)并证明。
  8. 埃及分数 \(\frac{19}{45}\) 的最少项表示。(答:\(\frac{1}{5} + \frac{1}{6} + \frac{1}{18}\))
  9. 为"骑士精神"写出完整 IDA* 并验证样例。
  10. 对模拟退火,解释为什么降温太快会得到差解、太慢会超时。

13. 推荐题单

难度 题目 考点
基础 洛谷 P1219 八皇后 回溯
基础 洛谷 P1433 吃奶酪 状压记忆化
基础 洛谷 P1032 字串变换 双向 BFS
基础 洛谷 P1379 八数码 康托 + BFS / A*
基础 洛谷 P1120 小木棍 剪枝集大成
基础 洛谷 P1731 生日蛋糕 上下界估计
提高 洛谷 P1763 埃及分数 迭代加深
提高 洛谷 P2324 骑士精神 IDA*
提高 洛谷 P2534 铁盘整理 IDA*
提高 洛谷 P4799 世界冰球锦标赛 折半
提高 洛谷 P3067 Balanced Cow Subsets 折半 + 去重
提高 洛谷 P2831 愤怒的小鸟 状压 + 最小未覆盖
提高 洛谷 P2668 斗地主 搜索 + 贪心
提高 洛谷 P1092 虫食算 搜索顺序 + 可行性
提高 洛谷 P1312 Mayan 游戏 模拟 + 剪枝
提高 洛谷 P1074 靶形数独 MRV + 位运算
提高 洛谷 P4929 DLX 模板 精确覆盖
提高 洛谷 P1784 数独 DLX / 位运算
提高 洛谷 P4011 孤岛营救问题 分层 BFS
提高 洛谷 P2322 最短母串 AC 自动机 + BFS
提高 洛谷 P1337 平衡点 模拟退火
提高 洛谷 P2210 Haywire 模拟退火
挑战 洛谷 P3959 宝藏 状压 / 随机化
挑战 洛谷 P5507 机关 IDA* / 双向 BFS 大状态
挑战 POJ 3460 Booksort IDA* 估价设计
挑战 洛谷 P2483 k 短路 A* / 可持久化堆

附录:一页纸总结

四要素:状态、转移、初始与目标、判重。

剪枝六法:可行性(尽早)、最优性(下界越紧越好)、排除冗余(组合 / 对称 / 相同元素)、搜索顺序(约束强的先、MRV)、记忆化、上下界预处理。

选算法:最短步数 → BFS;深度未知答案浅 → IDDFS;有估价 → IDA(无内存)/ A(有内存);终点明确 → 双向;\(n \le 40\) 子集 → 折半;精确覆盖 → DLX;没有正解 → 退火 / 随机化 + 卡时。

估价:松弛问题的最优解;可采纳保证正确,一致性允许标记已访问。

判重:康托(排列)、位掩码(集合)、Zobrist(棋盘)。

估算:Knuth 随机路径法。搜索能否过,先估再写。

posted @ 2026-09-20 10:04  ylzqwq  阅读(37)  评论(0)    收藏  举报