精确覆盖问题
给定一个由0,1组成的矩阵,找到一个行的集合,使得这些行的每一列都有且只有一个1
可以用DLX解决,DLX 里的X就是X算法:
http://en.wikipedia.org/wiki/Knuth's_Algorithm_X
int remove(int a)
{
删除列a;
删除列a上为1的每一行;
}
int resume(int a)
{
remove的反操作;
}
int dfs()//X算法递归程序
{
if(矩阵为空) return 1;
找到1最少的列a;
remove(a)
for i=a列上的为1的每一行
{
remove(i行上为1的每一列);
if(dfs()==1) return 1;
resume(i行上为1的每一列);
}
resume(a);
return 0;
}
DL 是dancing links,是种用十字链表提高X算法效率的技巧,具体内容可去搜论文,或者直接看程序
或者看这个~
http://en.wikipedia.org/wiki/Dancing_Links
矩阵的十字链表 表示:
0 1 1 0
1 0 1 1
0 1 0 0
表示为
![](http://hi.csdn.net/attachment/201108/26/0_1314331718mydA.gif)
程序如下:
#include <vector>
#include <stdio.h>
#include <string.h>
using namespace std;
const int R=16,C=301;
int mat[R][C];
struct node
{
node *up,*down,*left,*right;
int i,j; //记录每个节点的行和列
}clo[C],row[R],head,all[R*C]; //图中 蓝色的节点,黄色的节点,红色的节点,紫色的节点
int num[C];//记录每列1的个数
void remove(int c)//删除一列和关联的行
{
if(c==-1) return ;//-1的列是row所在的列,不删除
clo[c].right->left=clo[c].left;
clo[c].left->right=clo[c].right;
for(node *ip=clo[c].down;ip!=&clo[c];ip=ip->down)
{
for(node *jp=ip->right;jp!=ip;jp=jp->right)
{
if(jp->j==-1) continue;//跳过-1的列
num[jp->j]--;
jp->up->down=jp->down;
jp->down->up=jp->up;
}
}
}
void resume(int c)//恢复
{
if(c==-1) return;
clo[c].right->left=&clo[c];
clo[c].left->right=&clo[c];
for(node *ip=clo[c].down;ip!=&clo[c];ip=ip->down)
{
for(node *jp=ip->right;jp!=ip;jp=jp->right)
{
if(jp->j==-1) continue;
num[jp->j]++;
jp->up->down=jp;
jp->down->up=jp;
}
}
}
int dfs()
{
if(head.right==&head) return 1;
node *minp; int min=1000000000;
for(node *ip=head.right;ip!=&head;ip=ip->right)
{
if(num[ip->j]<min)
{
min=num[ip->j];
minp=ip;
}
}//找到1最少的列
if(min==0) return 0;//如果有全为0的列,返回失败
remove(minp->j);
for(node *ip=minp->down;ip!=minp;ip=ip->down)
{
for(node *jp=ip->right;jp!=ip;jp=jp->right)
remove(jp->j);
if(dfs()==1) return 1;
for(node *jp=ip->left;jp!=ip;jp=jp->left)//恢复的顺序因该和删除的顺序相反
resume(jp->j);
}
resume(minp->j);
return 0;
}
void make(int m,int n)//生成十字链表
{
int i,j,k;
int newn=-1;
memset(num,0,sizeof(num));
head.left=&head;
head.right=&head;
head.up=&head;
head.down=&head;
head.i=-1;
head.j=-1;//初始化红色的节点
for(i=0;i<n;i++)
{
clo[i].i=-1;
clo[i].j=i;
clo[i].up=&clo[i];
clo[i].down=&clo[i];
clo[i].left=&head;
clo[i].right=head.right;
clo[i].left->right=&clo[i];
clo[i].right->left=&clo[i];
}//加入蓝色的节点
for(i=0;i<m;i++)
{
row[i].i=i;
row[i].j=-1;
row[i].left=&row[i];
row[i].right=&row[i];
row[i].up=&head;
row[i].down=head.down;
row[i].up->down=&row[i];
row[i].down->up=&row[i];
} //加入黄色的节点
for(i=0;i<m;i++)
{
for(j=0;j<n;j++)
{
if(mat[i][j]==0) continue;
newn++;
num[j]++;
all[newn].i=i;
all[newn].j=j;
all[newn].down=&clo[j];
all[newn].up=clo[j].up;
all[newn].up->down=&all[newn];
all[newn].down->up=&all[newn];
all[newn].right=&row[i];
all[newn].left=row[i].left;
all[newn].right->left=&all[newn];
all[newn].left->right=&all[newn];
}
}//加入紫色的节点
}
int main()
{
int m,n;int i,j,k;
for(;;)
{
if(scanf("%d%d",&m,&n)!=2) break;
for(i=0;i<m;i++)
for(j=0;j<n;j++)
{
scanf("%d",&mat[i][j]);
}
make(m,n);//由mat生成十字链表
if(dfs())
{
printf("Yes, I found it\n");
}
else
{
printf("It is impossible\n");
}
}
return 0;
}