HUSTOJ1017_Exact cover _跳舞链DLX的精确覆盖

题意

给一个 n * m 的图,每一个格子上写着 1 或 0。要求选出一个行的集合,由这个行集合构成的子图中,每一列有且只有一个 1。输出集合元素个数并输出集合,或输出NO。

思路

第一个DLX,学到了。
这类问题的学名叫精确覆盖问题。DLX准确地讲是一种数据结构,可以高效地进行矩阵的递归和回溯。
DLX详解:http://www.cnblogs.com/grenet/p/3145800.html
kuangbin的模板:http://www.cnblogs.com/kuangbin/p/3752854.html

题目链接

http://acm.hust.edu.cn/problem/show/1017

AC代码

可以说是默写了一遍 kuangbin 的模板

#include<cstdio>
#include<iostream>
#include<cstring>

using namespace std;

const int maxn = 1010;                                              //最大行数 
const int maxm = 1010;                                              //最大列数 
const int maxv = maxn * 100;                                        //最大节点数 

//Dancing Links 类 
struct DLX
{
    int n, m, size;                                                 //行数、列数、节点数 
    int U[maxv], D[maxv], L[maxv], R[maxv], Row[maxv], Col[maxv];   //上、下、左、右指针,行、列号 
    int S[maxm], H[maxn];                                           //列的节点个数,行的头指针 
    int ansd, ans[maxn];                                            //答案栈 

    void init(int _n, int _m)                                       //初始化函数 
    {
        n = _n, m = _m;

        for(int j= 0; j<= m; j++)
        {
            Row[j] = 0, Col[j] = j;                                 //行、列号 

            U[j] = D[j] = j;                                        //上下指针 
            S[j] = 0;

            L[j] = j - 1, R[j] = j + 1;                             //左右指针
        }
        L[0] = m, R[m] = 0;                                         //端点左右指针 

        size = m;                                                   //节点个数(初始 = 列数)(附加节点) 

        for(int i= 1; i<= n; i++)                                   //行的头指针 
            H[i] = -1;
    }

    void link(int r, int c)                                         //向链表中插入节点 
    {
        size ++;                                                    //节点数目 
        Row[size] = r, Col[size] = c;                               //行号、列号 

        S[c] ++;                                                    //插入列链表 
        D[size] = D[c];
        U[D[c]] = size;
        D[c] = size;
        U[size] = c;

        if(H[r] == -1) H[r] = L[size] = R[size] = size;             //将节点插入行链表 
        else{                                                       //第一个节点之后 
            R[size] = R[H[r]];
            L[R[H[r]]] = size;
            L[size] = H[r];
            R[H[r]] = size;
        }
    }

    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= R[i]; j!= i; j= R[j])
            {
                U[D[j]] = U[j], D[U[j]] = D[j];
                S[Col[j]] --;
            }
    }

    void resume(int c)                                              //恢复列函数(同时恢复列中节点所在的行 
    {
        R[L[c]] = L[R[c]] = c;                                      //列恢复 

        for(int i= U[c]; i!= c; i= U[i])                            //恢复列中节点所在的行 
            for(int j= L[i]; j!= i; j= L[j])
            {
                U[D[j]] = j, D[U[j]] = j;
                S[Col[j]] ++;
            }
    }

    bool Dance(int d)                                               //搜索函数 
    {   
        if(R[0] == 0)                                               //剩余列为空,搜索成功 
        {
            ansd = d;
            return true;
        }

        int c = R[0];                                               //从节点少的列开始搜索,可以减少状态数 
        for(int j= R[0]; j!= 0; j= R[j])
            if(S[j] < S[c]) c = j;

        remove(c);                                                  //删除当前列 

        for(int i= D[c]; i!= c; i= D[i])                            //遍历选取当前列中节点所在的行 
        {
            ans[d] = Row[i];                                        //行号压入答案栈 
            for(int j= R[i]; j!= i; j= R[j]) remove(Col[j]);        //删除行中节点对应的列 
            if(Dance(d+1)) return true;                             //向下一层搜索 
            for(int j= L[i]; j!= i; j= L[j]) resume(Col[j]);        //搜索失败的话还原当前行 
        }

        resume(c); 
        return false;
    }
};

int n, m;
DLX g;

int main()
{
    while(cin >> n >> m)
    {
        g.init(n, m);                           //初始化 

        for(int i= 1; i<= n; i++)               //构造链表 
        {
            int num;
            cin >> num;

            while(num --)
            {
                int j;
                cin >> j;
                g.link(i, j);
            }
        }

        if(g.Dance(0)){                         //DLX 
            printf("%d ", g.ansd);

            for(int i= 0; i< g.ansd; i++)
                printf("%d ", g.ans[i]);

            printf("\n"); 
        }
        else printf("NO\n");
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值