【搜索】Dancing Links——01矩阵

转载 2012年03月28日 20:22:04

本文转载自 

ccy1991911的博客


本来ccy是决定放弃Dancing Links的,结果第二天吴老师又哈来一堆关于Dancing Links的资料,于是,ccy又被埋了!~

    额,到现在为止,ccy就会01矩阵,这里,写一下我学习01矩阵这个问题时操作上的一些东西。

    ccy对于01矩阵的操作想的比较清楚,但是,对于效率的一些分析还是很茫然,吴老师说,我们长期处于知其然而不知其所以然状态中。

    这是Dancing Links论文的链接,反正ccy看起很累就是了:http://sqybi.com/works/dlxcn/

**************************************************************************************************

    问题描述:给定一个由0和1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1?

    如:6 7
        0010110
        1001001
        0110010
        1001000
        0100001
        0001101

    其中,行集合{1,4,5}是一解。

 

    拿着这题,在不考虑啥时间复杂度效率问题的时候,很明显,用暴搜肯定是搜得出来的。现在,我们就来看下暴搜要怎样搜。(虽然,不管怎么搜,貌似都搜都出来。)

    如果01矩阵为空,问题解决;

    否则

        选取一个列c(保证列c上一定有1);

        选取一行r,满足01矩阵中A[r][c]==1;

            删除列j满足A[r][j]==1;

            删除行i满足A[i][j]==1;

    在不断减小01矩阵下,递归进行上述操作。

    这是个很明显的暴搜方案。

 

    Dancing Links同样在暴搜,只是,舞得很漂亮。

   

    DLX有种压缩的感觉。我们保存的不是整个矩阵,而是矩阵里1的关系。对于每个1,我们生成一个新的结点,结点包括这几个域:

    1、链表连接:L[x],R[x],U[x],D[x],分别表示它的上下左右是哪个结点。

    2、坐标标号:nRow[x],nCol[x],表示结点x的行号列号。

    在这个表里,我们还要做两个表头,列表头和行表头,可以直接把它想成平时链表里的哨兵结点,但是又有那么一点点不同。对于每行每列而言,我们让链尾连向那个哨兵结点,形成一个环,即,每列每行又是一个单独的双向链环。

    还需要一个大表头head,它连接着列表头和行表头。

    因为,我们是给每个1新建结点,对于表头同样是新建,所以,需要建数组表示第几行的行表头的结点标号是多少,第几列的列表头的结点标号是多少。有数组:Rd[r],Cd[c]。

    下面,我们还需要一个S[c]数组,表示,第c列上有S[c]个1。

 

    现在,看我们那个朴素的搜索,把它用上面建的这些变量来实现。

 

    先说明一下,这个诡异的链表建出来究竟是什么样子。

    如上面的样例:

    
    (ccy辛苦画滴图呀!!!!)

    绿色的是行表头和列表头,右下角那个绿色的是大表头。

    蓝色的是1结点。

    红色是链表的链接情况。

    注意两条粉红色的连线,这里只是个行和列的示意,说明的是每个行每个列其实是单独的一个环。

 

    在做dfs操作前,我们需要把这个表建出来。

    对于每个方框,我们分配一个结点空间给它,并有自己的标号。

    首先建行,再建列,再建读入数据中的那些1结点。

    有图:

     

    关于选确定的列c,我们总是选S[c]最小的一个。

    论文上阐述了好多,ccy就茫然了好多。

    我们每选取一行后,就会删除一些其他的行,使得S[]里的一些元素一定变小。而在列c的前提下,我们选取的行是要枚举的,所以,枚举数量当然越小越好。

 

    下面,我们把这个搜索的流程dfs()再写一下。

 

    如果R[head]==head,则找到一个解;

    否则

        选取一个列c;

        删除列c;

        对于每个r=D[c],D[D[c],……,当r!=c,

            选取当行nRow[r],加进答案集合;

            对于每个j=R[r],R[R[r],……,当j!=r,

                删除列Cd[nCol[j]];

            dfs();

            把nRow[r]从集合里取出;

            对于每个j=L[r],L[L[r]],……,当j!=r,

                取消删除列Cd[nCol[j]];

        取消删除列c;

 

    选取列c:

    int minnum=INT_MAX;
    int c=R[head];
    for (int i=R[head];i!=head;i=R[i])
    {
        if (!nCol[D[i]]) continue;
        if (S[nCol[D[i]]]<minnum)
        {
            minnum=S[nCol[D[i]]];
            c=i;
        }
    }

 

    删除列c:把c从表头删除,通过c的列链表去删除c链表的所有行。

    L[R[c]]=L[c];
    R[L[c]]=R[c];

    for (int i=U[c];i!=c;i=U[i])
        for (int j=L[i];j!=i;j=L[j])
        {
            if (!nCol[j]) continue;
            U[D[j]]=U[j];
            D[U[j]]=D[j];
            S[nCol[j]]--;
        }

 

    取消删除列c:

    for (int i=D[c];i!=c;i=D[i])
        for (int j=R[i];j!=i;j=R[j])
        {
            if (!nCol[j]) continue;
            S[nCol[j]]++;
            U[D[j]]=D[U[j]]=j;
        }
    L[R[c]]=R[L[c]]=c;

    这里,我们删除的方向是相反的,这里也是Dancing Links最精彩的地方。

    其实行恢复并不一定要相反,因为j的移动方向和for循环里的操作方向是垂直的,但是,列恢复一定要相反。

    如图:

     
    我们是从上到下进行删除的,这是删除后的图,打了叉的连线代表已经不存在。

    如果,我们恢复的时候依然从上到下,恢复出来的图就是这个样子:

    


    题目要求,保证选出来的行集合,每列都有1,这个,要怎么实现呢?

    开始,我纠结过这个问题,其实,有很多解决方法,就是硬开变量来记录都OK。只是,下面这个代码在处理这里的时候很巧妙,并不需要特别判断与记录。

    我们每次选取一行放进集合时,就要把那一行有1的列删去。抓住这点,如果没有1,就没有做列删除的操作,由于一开始就是建了所有列的列表头的,那么,即使做完都没还有列表头存在,那就证明该列根本就没有1,也就是在判断链表是否为空R[head]==R时,就可以做出每列是否都有1的判断。

 

ccy代码:

#include<iostream>
using namespace std;

const int maxr=1001;
const int maxc=1001;


int L[maxr*maxc],R[maxr*maxc],U[maxr*maxc],D[maxr*maxc];

int S[maxc];

int Rd[maxr],Cd[maxc];

int nRow[maxr*maxc],nCol[maxr*maxc];

int head;


int n,m,cnt;


int best[maxr],ans[maxr];

void ins_row(int r)
{
    cnt++;

    U[D[head]]=cnt;
    D[cnt]=D[head];
    U[cnt]=head;
    D[head]=cnt;

    L[cnt]=R[cnt]=cnt;

    Rd[r]=cnt;
}

void ins_column(int c)
{
    cnt++;

    L[R[head]]=cnt;
    R[cnt]=R[head];
    L[cnt]=head;
    R[head]=cnt;

    U[cnt]=D[cnt]=cnt;

    Cd[c]=cnt;
}

void ins_node(int r,int c)
{
    cnt++;

 
    int rhead=Rd[r];
 
    int chead=Cd[c];

    L[R[rhead]]=cnt;
    R[cnt]=R[rhead];
    L[cnt]=rhead;
    R[rhead]=cnt;

    U[D[chead]]=cnt;
    D[cnt]=D[chead];
    U[cnt]=chead;
    D[chead]=cnt;

    S[c]++;
    nRow[cnt]=r;
    nCol[cnt]=c;
}

void init()
{
 
    head=0;
    L[head]=R[head]=U[head]=D[head]=head;
 
    cnt=0;

    scanf("%d%d",&n,&m);
 
    for (int i=1;i<=n;i++)
        ins_row(i);
 
    for (int i=1;i<=m;i++)
        ins_column(i);
    char ch;
    for (int i=1;i<=n;i++)
    {
        scanf("%c",&ch);
        for (int j=1;j<=m;j++)
        {
            scanf("%c",&ch);
            if (ch=='1') ins_node(i,j);
        }
    }
}

void Remove(int c)
{
 
    L[R[c]]=L[c];
    R[L[c]]=R[c];

 
    for (int i=D[c];i!=c;i=D[i])
        for (int j=L[i];j!=i;j=L[j])
        {
            if (!nCol[j]) continue;
            U[D[j]]=U[j];
            D[U[j]]=D[j];
            S[nCol[j]]--;
        }
}

void Resume(int c)
{
 
    for (int i=U[c];i!=c;i=U[i])
        for (int j=R[i];j!=i;j=R[j])
        {
            if (!nCol[j]) continue;
            S[nCol[j]]++;
            U[D[j]]=D[U[j]]=j;
        }
 
    L[R[c]]=R[L[c]]=c;
}

void dfs()
{
 
    if (R[head]==head)
    {
        if (ans[0]>best[0])
            for (int i=0;i<=ans[0];i++)
                best[i]=ans[i];
        return;
    }

 
    int minnum=INT_MAX;
    int c;
    for (int i=R[head];i!=head;i=R[i])
    {
        if (!nCol[D[i]]) {c=i; return;}
        if (S[nCol[D[i]]]<minnum)
        {
            minnum=S[nCol[D[i]]];
            c=i;
        }
    }

 
    Remove(c);
 
    for (int i=U[c];i!=c;i=U[i])
    {
        ans[0]++;
        ans[ans[0]]=nRow[i];
  
        for (int j=L[i];j!=i;j=L[j])
        {
            if (!nRow[j]) continue;
            Remove(Cd[nCol[j]]);
        }
        dfs();
        ans[0]--;
  
        for (int j=R[i];j!=i;j=R[j])
        {
            if (!nRow[j]) continue;
            Resume(Cd[nCol[j]]);
        }
    }
 
    Resume(c);
}

void out_ans()
{
    for (int i=1;i<best[0];i++)
        for (int j=i+1;j<=best[0];j++)
            if (best[j]<best[i]) swap(best[i],best[j]);
    printf("%d\n",best[0]);
    for (int i=1;i<=best[0];i++)
        printf("%d ",best[i]);
}

int main()
{
    freopen("data.in","r",stdin);
    freopen("data.out","w",stdout);

    init();
    dfs();
    out_ans();

    fclose(stdin);
    fclose(stdout);
    return 0;
}

    这个代码是ccy在网上东拼西凑,最后综合了一下,形成自己的这种。

    本来找到一个代码,在建表的时候,没有先建行表头和列表头,而是遇到新的行列再建,原本两者没有什么大区别,但是,针对这题而言,我觉得我这样建还好些,而且,这样建的表和读入的数据对应得起来。

    在理解这个算法的时候,ccy有几个疑问,基本上上面都已经解释了,这里,我在列一下:

    1、删除与恢复的方向。

    2、c为什么要去S[]中的最小。

    3、怎样保证每列都有1。

    4、代码中标红色的一句。

       本来我找到的代码,这里的return写的是continue。试想,如果,有一次c的值真的是continue那里来的,那么一定没有解,而如果真的进了continue句话,这样的状态又不会再变,所以,一旦真有这种情况出现,说明,这样走下去肯定无解,因为,即使走下去,总有一次c的值会是从continue那里的i赋值而来,依然无解。

       这里,我直接用了return。

       原因是,如果列表头中存在这个表头,但是它的D[]有指着的是自己,那说明该列根本就不可能再出现1了,所以,再深入dfs下去也没有意义了,直接可以return掉。

 

    额,ccy继续加油……O(∩_∩)O~……

**************************************************************************************************

    有个地方,开始没看透,在Kruth的论文里其实就只有列表头,而这里,我还有行表头。现在正在做n皇后问题,这样,我就需要列、行、两个方向对角线的表头。这时,才发现,需呀与head关联的实际上只是列表头,这样来看,其实,我们也只需要列表头。不过,现在ccy还做不来只有列表头的呢。还是开个哨兵结点当表头比较好。O(∩_∩)O~


dancing links解决X问题的C++实现

X问题,也称精确覆盖问题,就是给定一个01矩阵,需要从中选取一些行组成一个子矩阵,这个子矩阵的每一列有且仅有一个1。这个问题听起来就知道很难,必须使用回溯算法来解决,但是我们知道回溯算法要提高效率,就...
  • xanxus46
  • xanxus46
  • 2015年11月13日 22:18
  • 962

简单易懂的Dancing links讲解(4)

DancingLinks的应用        把dancingLink应用于实际问题时,只有一个难点,就是如何把具体的问题转换为可以精确覆盖的01矩阵模型,一旦完成了这个步后,直接套用模板就可以解决问...
  • mu399
  • mu399
  • 2012年06月03日 13:00
  • 7822

Dancing Links 精确覆盖问题的快速dfs

引.精确覆盖问题: 给定一个矩阵0-1矩阵,如: 1 0 1 0 0 1 0 1 0 判断或输出一些行,这些行的在同一列上有且仅有...
  • rptotal
  • rptotal
  • 2012年02月28日 19:09
  • 2513

我的跳舞链 Dancing Links 模板

第一个模板——精确覆盖问题 题目:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=3038 为什么选择这题呢,因为它既可以...
  • fp_hzq
  • fp_hzq
  • 2011年09月22日 12:48
  • 1727

FZU_1686_神龙的难题(DancingLinksX重复覆盖)

Problem 1686 神龙的难题 Accept: 661    Submit: 1983 Time Limit: 1000 mSec    Memory Limit : 32768 KB ...
  • baidu_29410909
  • baidu_29410909
  • 2016年05月01日 00:04
  • 1566

【POJ3740】Easy Finding DLX(Dancing Links)精确覆盖问题

Dancing Links(DLX)的个人心得。
  • Vmurder
  • Vmurder
  • 2014年10月29日 15:11
  • 1449

【TJOI2014】拼图(puzzle) dancing links X

描述小 Z 最近迷上了拼图游戏,然而智商有限,他总是无法拼出完整图案。游戏是这样的:首先小 Z 会得到一些拼图碎片,然后他试着重新排列这些碎片使得它们组成一个大小为 4 ∗ 4 的正方形。如下图。注意...
  • qq_35866453
  • qq_35866453
  • 2017年03月25日 15:04
  • 155

简单易懂的Dancing links讲解(1)

最早接触Dancing Links的时候,是在csdn论坛上逛的时候,发现有人在研究数独程序,由于本人开发过数独游戏,就进去看了看,发现有人说用Dancing Links来求解数独最快了,于是我就决定...
  • u013007900
  • u013007900
  • 2015年02月10日 10:38
  • 427

hdu 3335 Divisibility(Dancing Links 重复覆盖)

Divisibility Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) T...
  • u011699990
  • u011699990
  • 2015年05月10日 00:30
  • 483

Dancing links ? Dancing links !

某次做JSOI的图论题 虐报其他人爆搜程序(废话) 原因就是利用了Dancing links基础-->>双向链表 ! 给出一个双向链表的模版 #include #include usi...
  • z635457712a
  • z635457712a
  • 2012年09月06日 22:23
  • 374
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:【搜索】Dancing Links——01矩阵
举报原因:
原因补充:

(最多只允许输入30个字)