学了算法竞赛进阶指南的DFS章节后,我对“状态”和“阶段”有了更深刻的理解。
阶段:问题被解决途中的所有情况就是阶段。比如我们要检查10个苹果中,有多少个苹果是坏的。我们一个个的检查。我们已经检查了5个苹果,那么这就是一个阶段,下一个阶段就是要检查第6个苹果。
状态:状态表示每个阶段开始所处的自然状况或者客观条件。当我们要检查第6个苹果时,需要知道 已经检查出的坏苹果数目、总共需要检查苹果的个数以及当前检查的苹果编号。
搜索问题就是对于每个阶段,我们有很多选择能到下一个阶段。比如求从10个不同苹果中,选择4个苹果的所有情况。我们面临的第一个阶段是:在这十个苹果中先任选一个,以此来到下一个阶段。从第一个状态到第二个状态共有10条分支。每个选择都是一个分支,状态就是节点,这样我们就能建立起一个“决策树”。
深度优先搜索
我们可以依次把每只小猫放到缆车上。
第一个思路是枚举缆车数量,再放小猫。每只小猫可以放到这些缆车中的任何一个。依次搜索每只小猫,会超时。仔细观察,对于第一只小猫,它放到任何一个缆车上都是等价的。因此会有很多冗余情况。
第二个思路是直接放小猫。对于每个状态,我们只需要知道是 枚举的是第几只小猫,有几架缆车和每个缆车上的小猫重量。对于第i只小猫,我们可以把它放到已有缆车的任意一个,或者给它一个新的缆车。这样搜索每只小猫,配合 最优性剪枝 和 可行性剪枝,就可以得到答案。
暴力方法:
对于每一个可以填的位置,填所有可能的合法的数。会超时。因此需要剪枝。
剪枝方法:
优化搜索顺序:我们优先选择 可选择的合法数较少 的位置,因为这样会让分支数减少。
如何快速找到每个位置能够选择的合法数?我们可以用二进制数来代替bool数组,对于每个位置(i, j),我们已经记录第i行、第j列、第(i / 3, j / 3)个九宫格的所有可以选择的数,求交集,就可以得到位置(i, j)能够选择的合法数。求交集可以用&运算,能大大降低时间复杂度。
和数独同样的思路。
剪枝
剪枝方法:
一.优化搜索顺序:
在拼接一根木棍时,优先选择大的木棒。这样会让分支数减少,从而更快的得到答案,也就能更快的进行可行性剪枝。
二.排除等效冗余
- 在拼接一根木棍时,先选x再选y 和 先选y再选x 两者相等。我们可以规定拼接一根木棍时,只能从大到小选。
- 如果长度为len的木棒失败,那么后面长度为len的木棒也一定失败
- 在拼接空木棍时,如果所选的第一个木棒失败,就直接判定该状态失败。因为该木棒不能被放到任何一个空木棍上。
- 把木棍拼接好后,接下来的搜索失败,那么直接判定该状态失败。
0.上下界剪枝:枚举蛋糕时,我们枚举的是蛋糕的半径和高度。我们要对枚举蛋糕的半径和高度设置范围:我们由题目可以知道半径和高度的最大值不能超过下层蛋糕的半径和高度;由 1.优化搜索顺序 可知,我们可以记录枚举的蛋糕的体积,设还需要添加的蛋糕体积假设为Vpi, V = r * r * h,当h最小时,r最大为 (V / h) ^(1/2),此时的h等于V / (r * r)。最小值不能低于剩余层数。
for (int r = min((int)sqrt(n - v), R[cnt + 1] - 1); r >= cnt; r --)
for (int h = min((n - v) / r / r, H[cnt + 1] - 1); h >= cnt; h --)
1.优化搜索顺序:从下往上枚举蛋糕。半径对体积的贡献较大,优先枚举蛋糕的半径。。因为最下层的蛋糕能占据更大的体积,使分支数减少。
2.可行性剪枝:
- 当枚举的蛋糕的体积大于题目给定的体积,或者表面积大于已经求得的表面积时,回溯。
- 需要公式推导,难度较大。算法进阶指南有详细推导。
可行性剪枝:从右往左遍历加法算式的每一列。如果列上2个数的和sum % n或者(sum + 1) % n 和第3个数不相等,那么就一定不成立。如果最大数有进位,那么一定不成立。
搜索性剪枝:配合可行性剪枝,先枚举算式最右边的字母可能等于的数字。
用string会超时。可以将搜索string的结果保存到一个表中。