<福州集训之旅Day1> | 枚举与DFS |

新专题<福州集训之旅>

啦啦啦新专题
csdn终于自带分割线啦这里写图片描述


<更新提示>

<第一次更新>主要记录知识点和例题,不附带代码。


<正文>

枚举法

含义很简单,即将所有解一一列出来,利用了计算机运算速度快的特点,一一判断其可行性和最优性。同时,这也是OI界求解问题最基本的算法。

<例1>
填数游戏
这里写图片描述

ABC各代表一个1到9之间的数,确定ABC所有的可能性。
解析:在数学中,我们尝试用各种条件去推理ABC。但是ABC分别只是一个一位数,在OI中我们使用枚举发枚举每一种可能就能求出所有的答案。

<例2>

给定正整数A,B,L,试确定一个比例A’:B’,使得A’,B’互质,且满足A’,B’<=L, A‘/B’-A/B尽可能小。(1<=A,B<=1000000,1<=L<=100, A/B<=L)
解析:这道题看似是一道不简单的数学题,实际上很简单。利用L不大的特点,我们枚举所有分子分母小于L的分数即可。再和A/B比较,找出符合题意的最优解。关于分数的加减法就不成问题了吧。

<例3>

已知一个n*n的01矩阵你需要从中找到一个最大连续的子矩阵,该剧中内的所有元素都是1.
解析:这道题直接硬枚举的话,时间复杂度分别为(枚举左上点)O(n2),(枚举右下点)O(n2),(检查判断)O(n2),所以总复杂度为O(n6),显然,这个巨大的指数级复杂度是无法接受的,即使对这种方案的优化也避免不了O(n4)的时间复杂度,所以我们需要改变思路。
为了确定一个子矩阵,我们需要确定他的4条边。但是,为了降低时间复杂度,我们可以确定矩阵的上下两条边,再使用一个数组Ci表示上下两条边限制范围内每一竖列是否为全1列,这样找全1矩阵的问题就化解为了找Ci中连续为1的子序列,相当于对目标降了一维,代码的实现就有了意义。时间复杂度又降到了O(n3),到了我们能够接受的范围中。

<例4>
[NOIP2016魔法阵]
有m件魔法物品,第I件的魔法值为正整数Xi<=n,一个魔法阵由编号a,b,c,d的4样物品构成,且满足两个条件:
1| Xa < Xb < Xc < Xd
2| 2(Xd - Xc)=Xb - Xa < (Xc - Xb)/3
你的任务就是计算每样物品作为某个魔法阵的ABCD物品分别出现了几次。
1<=n<=15000,1<=m<=40000
解析:首先一个魔法阵与物品的编号无关,又不会出现两种魔法值相同的物品,又因为魔法值不会超过15000,所以可以用数组t记录每种魔法值的物品有几个,并再t数组上分析问题。分析题目中的条件,使Xd - Xc=i,那么Xb - Xa=2i,且Xc - Xb > 6i,故Xd - Xa > 9i,所以我们得知Xb - Xa最多有n/9中不同的可能,所以我们考虑枚举i的值。接着,枚举b物品的魔法值,a物品的魔法值也随之确定。再利用c物品魔法值至少比b物品魔法值高6i+1,从6i+1开始,用部分和进行优化。记数组r[k]表示再当前i值的情况下,只取c,d物品,且c物品魔法值>=k的方案数,d物品的魔法值也随之确定了,那么这个值可以按下标从大到小再O(n)的时间内求出。继续枚举Xb,再当前i的情况下b物品出现的次数即为t[Xb-2i]*r[Xb+6i+1],Xa=Xb-2i,所以a物品出现的次数为[Xb]*r[Xb+6i+1]。类似于上述方法,同样用O(n)的时间求c,d物品的魔法值,所以整个算法的时间复杂度即为O(n/9)*O(n)=O(n2/9),足以通过这道题。

小结

经过4道例题,我们可以总结出枚举法需要主要的特点:
保证最优解(或全部的解)能被枚举到;
枚举量不能过大;
对于枚举量的优化,我们可以通过减少或改变枚举对象,调整枚举顺序的方式。

DFS

显然,使用枚举法的情况要求对对象需要有比较清晰的结构和层次,当对于其他的枚举,for循环就不太行了。
所以我们就可以用到了DFS中的一种统一枚举方法——递归回溯法。它的思想是从某一步开始,先朝某个方向达到新状态,当搜索完新状态时,撤销这一步,并朝其他方向尝试,指导找到了结果或试完了所有可能。

<例1>
全排列输入正整数n,输出1到n这n个数字的全排列。
解析:大致递归回溯思想如下:
1 n个数是否已经填完,用一个形参记录。
2 一次顺序取数,直到取满,并用used数字记录是否已经使用过该数字。
3 继续递归构造数列。
4 还原used,尝试其他方案。
当然,全排列在c++中可以用next_permutation函数配合dowhile语句直接实现。

<例2>

给定n个城市,其中某些城市直接有直接的道路相连,现在给定两座城市S和T,寻找所有S到T直接不重复的路径。
解析:递归回溯的思想和全排列相似,具体如下:
1 判断是否到达了T城市,并进行输出或重复执行的操作。
2 记录:标记当前城市已经走到过。
3 依次递归尝试向深度搜索其他城市搜索。
4 回溯:令当前城市已经做到过的标记复原,返回上一层。
初始时对应S开始进行递归搜索。

<例3>

在一个n*m的局限里填入c中颜色,要求相邻两个格子的颜色不同。
解析:递归结构还是相同的,只需要保证相邻格子不冲突即可,按照c种颜色限制和颜色填充回溯搜索即可。

<例4>

给定4个1至13的正整数,运用四则运算得出24这个结果,输出所有方案。
解析:这是一道代码量较大,极其练手的题目,但是难度并不大,运用DFS和回溯的技巧对数进行运算,即每次从当前拥有的数中找到两个数并进行某种运算,再将结果放回数字集合中。DFS搜索的最后目标就是数字集合中只剩下24,为了还原每一步的操作,记录下运算即可。需要注意的事项是注意运算时产生的分数,合理储存,注意细节即可。

小结

这样,我们就能得出递归回溯的基本框架了:
1 判断当前状态是否为所要的状态,进行处理或回退。
2 再当前状态上枚举合法的方向,取一个未尝试过的方向,如果没有就退出。
3 沿改方向走一步,并记录改变状态。
4 再在新的状态上执行这个流程
5 回溯一步


<后记>
博客量越来越大了,希望以后能坚持。


<废话>
废话已经说不动了。

猜你喜欢

转载自blog.csdn.net/prasnip_/article/details/79275041
今日推荐