POJ 3740 Easy Finding 詳細講解


精確覆蓋問題

給定一個由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;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章