HUST 1017 Exact cover(DLX精确覆盖)·(转载)

题目链接
思路链接(转载)
代码链接(转载)

There is an N*M matrix with only 0s and 1s, (1 <= N,M <= 1000). An exact cover is a selection of rows such that every column has a 1 in exactly one of the selected rows.
Try to find out the selected rows.

Input

There are multiply test cases.
First line: two integers N, M(1<=N,M<=100);
The following N lines: Every line first comes an integer C(1 <= C <= 100), represents the number of 1s in this row, then comes C integers: the index of the columns whose value is 1 in this row.

Output

First output the number of rows in the selection, then output the index of the selected rows.
If there are multiply selections, you should just output any of them.
If there are no selection, just output “NO”.

Sample Input

6 7
3 1 4 7
2 1 4
3 4 5 7
3 3 5 6
4 2 3 6 7
2 2 7

Sample Output

3 2 4 6

大致题意
多组数据 n 行 m 列,接下来 n 行开头一个数,表示这行有 n 个数字是1,后面跟着k个数字表示这个数字的列,问最少需要几行让每一列都有1?

具体思路
这个案例输入之后就是
1 0 0 1 0 0 1
1 0 0 1 0 0 0
0 0 0 1 1 0 1
0 0 1 0 1 1 0
0 1 1 0 0 1 1
0 1 0 0 0 0 1
只要2,4,6行就可以让每一列都有1

这是DLX精准覆盖的题目,裸题,能搜到这题的人,想必也知道原题目的网站已经GG,提交不了,这里就拿了几位前辈的思路和代码给大家讲解,链接已经在文章顶部了,下面是个人对于前辈代码的一个解读

#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
const int maxm = 110;
const int maxnode = 110 * 110;
int L[maxnode], R[maxnode], U[maxnode], D[maxnode];
int C[maxnode],H[maxn],cnt[maxm],vis[maxn],ans[maxn],Row[maxnode];
int n,m,id,len;

void init()
{
    /*第一篇博客中的第一行C1-Cn的初始化,其id是0-m*/
    for(int i=0; i<=m; i++)
    {
        cnt[i] = 0;
        U[i] = D[i] = i;
        L[i + 1] = i;
        R[i] = i + 1;
    }
    R[m] = 0;//head和末尾元素链接
    id = m + 1;//输入的数字id从m+1开始,见27行
    memset(H, -1, sizeof(H));//促使话行状态元素
}

void Link(int r, int c)
{
    /*第一篇博客中1-16元素的添加,因为第一行的C初始化占据
    了0-m的id,所以这里从m+开始*,而1-m的id元素表示顶部的C*/
    cnt[c]++;//此列个数
    C[id] = c;//此id数字位于第几列
    Row[id] = r;//此id数字位于第几行
    U[id] = U[c];//按照栈的原理存储了每一列最底部的元素
    D[ U[c] ] = id;//最底部元素有了新的底部,链接
    D[id] = c;//最底部的元素链接回顶部的C
    U[c] = id;//此列栈底更新为最新插入的id
    Row[id] = r;
    if(H[r] == -1) H[r] = L[id] = R[id] = id;//如果这一行只有自己,那么自己指向自己
    else
    {
        L[id] = L[ H[r] ];
        R[ L[ H[r] ] ] = id;
        R[id] = H[r];
        L[ H[ r ] ] = id;
    }
    id++;//总个数下标++
}

void Remove(int Size)
{
    L[ R[Size] ] = L[Size];//这个列右边的下一个列等于这个列的左边
    R[ L[Size] ] = R[Size];//这个列左边的下一个列等于这个列的右边
    for(int i=D[Size]; i!=Size; i=D[i])//与此相关的行列全部抹除
    {
        for(int j=R[i]; j!=i; j=R[j])
        {
            U[ D[j] ] = U[j];
            D[ U[j] ] = D[j];
            cnt[ C[j] ]--;//此列个数减去
        }
    }
}

void Resume(int Size)
{
    /*删除的时候这些被删除的C列元素里的双指针并没有删除,按着双指针
    回溯回去,重新建立删除的链表*/
    for(int i=D[Size]; i!=Size; i=D[i])
    {
        for(int j=R[i]; j!=i; j=R[j])
        {
            U[ D[j] ] = j;
            D[ U[j] ] = j;
            cnt[ C[j] ]++;
        }
    }
    L[ R[Size] ] = Size;
    R[ L[Size] ] = Size;
}

bool DLX(int k)
{
    int pos,mm = maxn;
    if(R[0] == 0)//head指向自己,元素没有剩下,输出
    {
        len = k;//返回个数
        return true;
    }
    for(int i=R[0]; i; i=R[i])//选取列数个数最少的那一列开始递归
    {
        if(mm > cnt[i])
        {
            mm = cnt[i];
            pos = i;
        }
    }
    Remove(pos);//移除此列相关元素
    for(int i=D[pos]; i!=pos; i=D[i])//此列包括哪几行,依次循环
    {
        ans[k] = Row[i];//第一个行元素
        for(int j=R[i]; j!=i; j=R[j]) Remove(C[j]);//从左往右移除此行其余列元素
        /*在进入下次递归之前,按着循环会移除所有的相关元素*/
        if(DLX(k + 1)) return true;
        /*此层递归后要把移除的元素给添加回去,保证下一次递归*/
        for(int j=L[i]; j!=i; j=L[j]) Resume(C[j]);
    }
    Resume(pos);//这个数右边的下一个数等于这个数的左边
    return false;
}

int main()
{
    int u,v,a;
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for(int i=1; i<=n; i++)
        {
            int xx;
            scanf("%d",&xx);
            for(int j=1; j<=xx; j++)
            {
                int yy;
                scanf("%d",&yy);
                Link(i, yy);
            }
        }
        bool ans1 = DLX(0);
        if(ans1 == false) printf("No\n");
        else
        {
            printf("%d",len);
            for(int i=0; i<len; i++)
            {
                printf(" %d",ans[i]);
            }
            puts("");
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值