算法 {精确覆盖, 重复覆盖}
@LOC_COUNTER = 3
{精确覆盖, 重复覆盖}
定义
一个 M ∗ N M*N M∗N的01矩阵 (每一行 即是一个2进制的数)
精确覆盖: 选择最少的行, 使得这些行的或运算 ∣ | ∣结果 等于 ( 1 < < N ) − 1 (1<<N)-1 (1<<N)−1, 且任意两个行的与运算 & \& & 等于 0 0 0;
重复覆盖: 选择最少的行, 使得这些行的或运算 ∣ | ∣结果 等于 ( 1 < < N ) − 1 (1<<N)-1 (1<<N)−1;
算法: 状压DP
定义
对于
M
∗
N
M*N
M∗N的01矩阵, 当
N
≤
20
N \leq 20
N≤20很小, 那么 每一行 就可以用一个int
来表示;
使用状态DP的时间为: 2 N ∗ k 2^N * k 2N∗k, 其中 k k k为 最多有多少个行 会同时包含同一个列 (显然 k k k最大为 M M M);
-{ 重复覆盖
DP(i)
表示: 从所有行里, 选择若干个行 且这些行的重复覆盖 的状态为i
;
这句话是错误的, 其实 他就是序列: SET(i)
里的元素: 元素为行的集合 (注意, 与以往DP不同, 这里不是序列 而是集合);
.
其实, DP集合里的元素 一定是序列, 因为 你遍历DP的顺序 就是DAG, 而DAG就是个序列; 因此 你在遍历这个DAG序列的过程中 是怎么选择元素的, 自然也是个序列;
.
MARK: @LOC_0
;
DP的递推: 当前状态为cur
, 令col
为 cur
二进制里 最低位的0
的下标, 令STs[ col]
为: 所有col
列为1
的行;
.
则cur | (st : STs[ col])
就是更新的下个状态;
-}
-{ 精确覆盖
和重复覆盖相同, 只是在DP递推时, 多了一个: (cur & st) == 0
的条件;
-}
性质
MARK: @LOC_2
;
以重复覆盖为例 (精确覆盖也一样), 在DP的递推时 我们完全可以使用cur | 所有行
来更新下个状态, 即遍历所有行, 但这样的时间 会是
M
M
M, 因为
k
≤
M
k \leq M
k≤M 因此使用
k
k
k是更好的;
.
参见LINK: https://editor.csdn.net/md/?articleId=130996850
; 这道题M = N^2
, 如果你用(1<<N) * M
会超时的, 而题目有个性质 即每个列 最多会有N
的行 覆盖他, 因此 可以优化成(1<<N) * N
;
但这确实增加了难度, 我们分析下 为什么可以这样优化;
该优化的核心 在于 答案只用到(1<<N)-1
这个状态;
.
举个例子, 只有1行010
, 那么有效的状态只有: 000
; 因为在000
时 此时ind = 0
(即0
所在的最低位) 他并不会更新其他后驱节点 (因为没有形如??1
的行); 但是显然, 其实010
这个状态 是合法的, 而且DP[010]
应该为1, 但是在我们的算法里 他就是非法的;
为什么会这样呢? 为什么对于当前cur
状态, 我们只需更新ind
位为1的这种行 而不更新ind
为0
的行呢?
这要和我们的目的相结合, 我们的目的 就是得到DP[ (1<<N)-1]
这一个状态, 不会用到任何其他状态;
使用反证法, 设当前状态是00111
, 有2个行10000, 01000
, 我们只会更新01111
这个状态, 不会更新10111
; 假如答案一定来自10111
而非01111
, 那么 因为最终答案一定是11111
, 所以 即使是10111
他也需要去满足里面的那个0
, 而这一步 次序可以翻转的, 即00111 -> 10000 -> 01000
一定可以变成00111 -> 01000 -> 10000
;
比较下 哈密顿路径的状压DP (为什么他不能优化): LINK: (https://editor.csdn.net/md/?not_checkout=1&articleId=130774546)-(@LOC_1)
;
@DELI;
错误
@DELI;
MARK: @LOC_1
;
重复覆盖时, 我们是以curDP -> nexDP
即去更新后驱节点, 能否反过来呢? 即划分当前状态 去找前驱节点呢?
比如当前状态是111111
(他表示一个集合, 集合里所有元素的或操作结果为111111
), 我们找到该集合里的一个元素 比如是111000
, 那么 你可能认为 他的前驱节点 就是000111
, 这是错误的! 因为是重复覆盖 他的前驱节点 可以是???111
其中?
可以是0/1
;
.
举个例子, 所有行是110, 011
; 如果当前状态是111
(此时全局 只有DP(000, 011, 110)
是有效的), 那么按照你上面的做法 你的前驱节点会是001, 100
显然是非法的);
算法模板
*重复覆盖*;
int DP[ 1 << N];
unordered_set< int> Col[ N];
memset( DP, 0x3F, sizeof( decltype(DP[0])) * (1 << N));
DP[ 0] = 0;
for( int st = 0; st + 1 < (1 << N); ++st){
auto & curDP = DP[ st];
if( curDP == 0x3F3F3F3F) continue;
int ind = Power_to_bit[ (~st) & -(~st)];
for( auto row : Col[ ind]){
auto nex = row | st;
MIN_SELF_( DP[ nex], curDP + 1);
}
}
只有`DP[ (1 << N) - 1]`是正确的, 其他状态的DP值 是错误的;
@DELI;
*精确覆盖*
在上述代码基础上, 增加`if( (st & s) != 0) continue;`的限制, 其他都一样;
例题
LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=131009780
;
LINK: https://editor.csdn.net/md/?articleId=130996850
;