题型有两种:精确覆盖与重复覆盖。
Dancing Links
1. 废话
首先,它有几个名字:DLX,Dancing Links,十字链表,舞蹈链。
然后,它是一种数据结构。
精确覆盖是指每一列恰好有一个 1。
重复覆盖是指每一列至少有一个 1。
它解决的问题,一般矩阵规模较大,但 1 的个数较少。
可以通过题目来理解。
2. 精确覆盖问题
1)如何存储矩阵
使用十字链表。
对于每一个 1,都建立一个节点。
2)初始化
每一个节点的上下左右都连向该方向的最近链表。(如果没有,就循环找)。
实际使用时,可以按照一行一行的处理。
同时,对于最前面,我们建立一个哨兵,全为 1。
上面的操作就是初始化。
3)插入一行
建立当前行的前面和后面,插入时,直接选择即可。
这里有些难理解,我们结合代码讲。
1 | void add(int &hh,int &tt,int x,int y) |
最开始时,第一个点的右边和左边都是当前本身,所以初始时都是 idx,更新时直接将 tt 赋值为 idx,就串成了一个循环链表。
4)搜索
在 dfs 的过程中,从未选择的行中任意选择一行,搜索一行。
剪枝:
- 比较明显,直接选择包含 1 最少的一列选择一行。
- 在选择一列后,将该列删除。
- 在选择一行后,将该行包含 1 的列全部删除。
这里,我们发现,需要 2 个函数:删除一列和恢复一列。
5)删除一列
对于哨兵,可以直接删除。
但是,由于要满足”精确覆盖“,我们要将包含该列的行全部删掉。
6)恢复一列
7)Code
1 |
|
3. 例题
做 Dancing Links 的题,做法如下:
- 首先将题目的选择设为行。
- 然后将题目的限制设为列。
- 使用模板。
T1:数独2
1 |
|
T2:[NOI2005]智慧珠游戏
将每一种摆法的旋转、平移的所有方法,都做成一个行,然后将每一个格子、每一种方法都做成列。
代码比较繁琐,但是思路不太难。
4. 重复覆盖问题
与精确覆盖问题对比。
重复覆盖问题的规模较小,而精确覆盖问题的规模较大。
重复覆盖问题的答案较小,而精确覆盖问题的 1 个数较小。
为什么答案要求要比较小呢?
因为使用了 IDA* (迭代加深)。
算法流程如下:
选择一个行数最少的列
枚举当前列是 1 的行。
枚举当前行,递归。
对比精确覆盖问题,我们发现有不同。
于是,这个就会慢很多。
我们又有一个武器:IDA*。
加上这样一个句: if (k+h()>depth) return false;
这样,我们应该考虑怎样构造 h()
。
请注意,我们要保证 $h()\leq ans$。
遍历所有未被覆盖的列,选择当前列的所有行,但是计数只记一行。
这样,我们其实选择了更多,所以答案不可能更大。
上代码。
1 |
|
5. 例题
T1:破坏正方形
可以将火柴看做行,将正方形看做列,其实就是一个重复覆盖问题了。
具体实现时,注意正方形的计算,可能会有些繁琐,需要你认真找规律。
代码略。
T2:雷达
首先,我们发现,答案具有单调性,即小的可以,那么更大的半径一定可以。
二分到一个答案时,我们将雷达设为行,将城市设为列,如果一行一列为一时,代表该雷达可以覆盖该城市。
剩下就是一个重复覆盖的模板了。
注意卡常!
注意 h()
的时候不要清空,而是每一次使用一个新的编号,这样就可以避免清空。
1 |
|