【AHOI2013复仇】从一道题来看DFS及其优化的一般步骤和数组分层问题【转】
http://www.cppblog.com/MatoNo1/archive/2012/09/23/191708.html
———————————————————————————————————————————————————
普通DFS(不加迭代)的优化方法主要有:
(1)可行性剪枝,如果遇到已经无法产生可行解的情况就剪枝,有时候,可行性剪枝也可以指在搜索过程中对不合法情况的剔除;
(2)最优性剪枝:如果遇到已经无法产生比目前得到的最优解还要优的解的情况就剪枝,这其中包括启发式最优性剪枝(即分支限界),对目前解的进展情况作乐观估计,如果估计值都不能超越目前的最优解,则剪枝;
(3)通过改变搜索顺序,尽早得出较优解,从而为后面的剪枝提供余地;
(4)通过预处理等手段,提前得到启发值或者较优解,为后面的剪枝提供余地;
一般步骤:
(1)预处理,当然是写在最前面,在搜索前得到启发值等东东;
(2)搜索过程中,先写最优性剪枝(包括已经到达搜索终点了,也应该判断一下,提前排除不是最优解的情况);
(3)再判定搜索终点,如果到了,也不要马上更新解,而是要判定这个解是否符合要求,再更新;
(4)若未到终点,再写可行性剪枝;
(5)最后写搜索过程,包括需要改变搜索顺序的情况。
注意事项:数组的分层问题。
这是一个很严重的问题,因为分层出错很可能会导致一些很怪的错误出现,难以发现。在搜索题的调试技巧这一篇中,已经提到了这个问题,本题又涉及到了。
一般来说,搜索过程中使用的数组(包括变量,可以视为零维数组)可以分为以下三类:
(1)在搜索过程中,只会引用其值,不会修改的数组(比如预处理中得到的那些东东),相当于常量;
(2)在搜索过程中,会改变其值,但在搜索完毕后能改回原值的数组;
(3)在搜索过程中,会改变其值,且在搜索完毕后也改不回原值的数组。
对于(1)(2)类数组,不需分层,但对于第(3)类数组一定要分层。这是因为这些数组在多层搜索中都需要修改,而且搜索完了也改不回来,如果不分层,则当后面的搜索完毕以后,要继续搜索前面的,引用的将是后面的值,就会出错。
对于第(3)类数组,有的已经“自然分层”(每层搜索修改的元素都不一样),比如本题代码中的R[][]、C[][]。然而有的并没有自然分层,比如本题的len、pos[]、tmp[]等,因此对于这些数组,需要再加一维,对于不同层使用该维不同的元素即可。
下面是本题的算法:
使用DFS搜索每个位置的挡板是否需要放。搜索时,先搜竖的再搜横的,由于题目要求每个连通块都必须是矩形,因此对于竖的,如果该位置的上方两个相邻位置的横的没有全放(包括只放了一个),则该位置的竖的放不放取决于其上一行对应位置的竖的有没有放(第一行除外),如果两个横的都放了,则这里的竖的既可以放也可以不放(先搜不放的情况)。当然,一行的两个1之间必须至少有一个竖的,这是可行性限制。在搜横的的时候,按照该行所放的竖的,分成若干段,显然每一段要么下面都用横的封上,要么一个都不封上。其中,如果该段所在的连通块里面有1,且下一行对应位置也有1,则必须封上;若该段所在连通块里面没有1,则不能封上(因为不能有一个连通块里一个1都没有),否则可以自由选择封还是不封(当然也是先搜不封的)。这样一直搜到最后一行的竖的后,还要判断最后一行有没有连通块里没1,若没有,则为一个可行解。启发式优化:设D[i]为第i行及以后至少要几个挡板(若某行有K个1,则至少需要(K-1)个竖的;若某列有K个1,则至少需要(K-1)个横的,累加起来即可,显然这是乐观估计),D[i]可以预处理出来,当做启发值。