问题简介
精确覆盖问题:给定一个01矩阵,从中选出若干行,使得每一列恰好有一个1。求一个方案。
算法实现
搜索
每次选择一行,然后把这一行有1的列,在同一列中也有1的其它行,以及这一行本身从矩阵中除去。然后又变成了一个小的矩阵,一直递归至只剩一行。如果这一行全是1,则返回有解并输出选择的行编号,否则无解并恢复原来矩阵。如果搜索完毕后仍没有方案则无解。
下面给出一张大矩阵变小矩阵的图:
其中红圈圈的表示被除去的元素。(第1,2,5行、第1,2,4列)
至于方案每次记录当前选的是哪一行就好了。
DLX
我们可以发现:之前的搜索需要耗费大量的空间来存储每一个矩阵,而且还原起来非常麻烦,而DLX可以完美解决这个问题。它的空间是 O(n2) O ( n 2 ) 的,而且时间也非常快。也就是说,它根本不需要额外的空间来存储。
DLX在hihocoder1317的提示中写的非常详细,这里因为实在太懒不再赘述,只对几个重要的过程做简要说明。
Build()
Build()操作即把原来的矩阵加到DLX中。
刚开始把列头节点建好,再分别从横向、纵向两个方向指好各个指针。
inline void build(){
h->d=h->u=h->l=h->r=h,h->x=h->y=0,pre=h;//建head
for (int i=1;i<=m;i++){//建列头节点
p=&a[i]; p->u=p->d=p,p->x=0,p->y=i;
p->r=pre->r,p->l=pre,pre->r->l=p,pre->r=p,pre=p;
}
nd=0;
for (int i=1;i<=n;i++)//标号
for (int j=1;j<=m;j++)
if (mp[i][j]){
id[i][j]=++nd,c[nd].d=c[nd].l=&c[nd];
c[nd].r=c[nd].u=&c[nd],c[nd].x=i,c[nd].y=j;
}
for (int j=1;j<=m;j++){//纵向添加
pre=&a[j];
for (int i=1;i<=n;i++)
if (mp[i][j]){
nod *p=&c[id[i][j]]; p->d=pre->d,p->u=pre;
pre->d->u=p,pre->d=p,pre=p;
}
}
for (int i=1;i<=n;i++){//横向添加
pre=null;
for (int j=1;j<=m;j++)
if (mp[i][j])
if (pre==null) pre=&c[id[i][j]];
else{
p=&c[id[i][j]],p->r=pre->r,p->l=pre;
pre->r->l=p,pre->r=p,pre=p;
}
}
}
remove(x)
移除第x列。
首先将第x列的左右两列指针互指,然后不断往下让左右列元素互指,直到该列头节点d指针指向自己。
inline void rmv(int x){
nod *p=&a[x],*p1=p->d;
p->r->l=p->l,p->l->r=p->r;
while (p1!=p){//直到该列头节点d指针指向自己
nod *p2=p1->r;
while (p2!=p1)
p2->d->u=p2->u,p2->u->d=p2->d,p2=p2->r;
p1=p1->d;
}
}
resume(x)
恢复第x列。
和remove(x)相反即可。
inline void rsm(int x){
nod *p=&a[x],*p1=p->d;
p->r->l=p->l->r=p;
while (p1!=p){
nod *p2=p1->r;
while (p2!=p1)
p2->d->u=p2->u->d=p2,p2=p2->r;
p1=p1->d;
}
}
dfs(x)
主体过程。
首先判是否已经有解。
若h的右指针不是自己那么删除h的右指针指向的那一列,并枚举每一行(删除,判解(继续递归),恢复),恢复该列并返回无解。
bool dfs(int x){
nod *p=h->r,*p1=p->d;
if (p==h) return true;
if (p1==p) return false;
rmv(p->y);
while (p1!=p){
ans[x]=p1->x; nod *p2=p1->r;
while (p2!=p1) rmv(p2->y),p2=p2->r;
if (dfs(x+1)) return true;
p2=p1->l;
while (p2!=p1) rsm(p2->y),p2=p2->l;
p1=p1->d;
}
return rsm(p->y),false;
}
算法用途
DLX可以求数独。
戳这里
其他的暂时不清楚~~