精確覆蓋問題
給定一個由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
表示爲
程序如下:
#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;
}