HUST 1017 Exact cover(舞蹈链&不能为了ac而ac)

题目链接[kuangbin带你飞]专题三 Dancing Links A - Exact cover

题意

给定一01矩阵,问是否能够精确覆盖(就是选取任意行,这些行的1所在的列互不冲突且完整覆盖所有列),若有输出行号(要按递增顺序输出),否则输出NO。

思路

ps:两个礼拜前大略看了下舞蹈链(虽然英文名听起来更高端,但还是更喜欢它的中文名字),很精妙但也让人一看就惰性必生不愿再看,今天耐心再仔细理解了下,总算是a的刷题生涯第一道精确覆盖题(人有时候还是要逼自己一把,战胜惰性才能进步)。

没什么思路,标准的舞蹈链模版题,理解了舞蹈链就能a了。
学习舞蹈链,推荐此博文,相当清晰:跳跃的舞者,舞蹈链(Dancing Links)算法——求解精确覆盖问题
代码上有详细的注释,但有一处有必要分享一下。

for(int i=R[0]; i!=0; i=R[i])
        if(S[i] < S[col])
            col = i;

上面这段代码作用是每次选取元素最少的列进行操作,可以有效减少递归层数,从而加快程序效率。
因为本人好奇,取消了它试了一下,发现wrong(按理也应该是timelimit啊),一试再试,发现是得出的行号没有排序的原因,加上对结果的排序就好了。
但本人又好奇了,一再思索,终究还是想不出来上述代码为什么能够使结果递增序,于是做了一组数据:
5 5
2 4 5
3 1 3 4
1 5
1 2
2 1 5
测试后发现无论加不加上述代码结果都不是递增序的,也就是说上述代码只是加速的功能而已。
也就是说,测试数据太水,所有没加排序的代码都应该被wrong。(很好奇那么多题解为什么没有一个带排序的,希望自己不要成为为了ac而ac的人)。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <vector>

using namespace std;

const int N = 1009;
const int MAX = 1000009;

int U[MAX], D[MAX], L[MAX], R[MAX];//数组模拟链表指向(上下左右)
int C[MAX], M[MAX];//节点所在列与行
int S[N];//储存每列的元素数量
int H[N];//行头指针
int ANS[N];//结果保存数组

void link(int row, int col, int id)//将节点加入链表
{
    C[id] = col; M[id] = row;//记录行列
    U[id] = U[col]; D[U[col]] = id;//上下连接
    D[id] = col; U[col] = id;
    if(H[row] == -1)//左右连接(使用表头方便头插)
        H[row] = L[id] = R[id] = id;
    else
    {
        L[id] = L[H[row]]; R[L[H[row]]] = id;
        L[H[row]] = id; R[id] = H[row];
    }
    S[col]++;
}

void remove(int col)//删除列
{
    R[L[col]] = R[col];
    L[R[col]] = L[col];
    for(int i=D[col]; i!=col; i=D[i])
    {
        for(int j=R[i]; j!=i; j=R[j])
        {
            U[D[j]] = U[j];
            D[U[j]] = D[j];
            S[C[j]]--;
        }
    }
}

void resume(int col)//恢复列(先删的后恢复,后删的先恢复,所以跟remove反向操作)
{
    R[L[col]] = col;
    L[R[col]] = col;
    for(int i=U[col]; i!=col; i=U[i])
    {
        for(int j=L[i]; j!=i; j=L[j])
        {
            U[D[j]] = j;
            D[U[j]] = j;
            S[C[j]]++;
        }
    }
}

bool dance(int k)
{
    if(!R[0])//列辅助数组为空表示已得解
    {
        printf("%d", k);
        sort(ANS, ANS+k);//对结果排序。
        for(int i=0; i<k; i++)
            printf(" %d", ANS[i]);
        printf("\n");
        return true;
    }
    int col = R[0];
    for(int i=R[0]; i!=0; i=R[i])//加速,上文已说明
        if(S[i] < S[col])
            col = i;
    remove(col);//删除列
    for(int i=D[col]; i!=col; i=D[i])//尝试该列每行一次做为解
    {
        ANS[k] = M[i];// 记录行号
        for(int j=R[i]; j!=i; j=R[j])//删除该行元素说相关的列
            remove(C[j]);
        if(dance(k+1))
            return true;
        for(int j=L[i]; j!=i; j=L[j])//恢复
            resume(C[j]);
    }
    resume(col);//恢复列
    return false;
}

int main()
{
    int n, m;
    while(~scanf("%d%d", &n, &m))
    {
        for(int i=0; i<=m; i++)//初始化
        {
            L[i+1] = i;
            R[i] = i+1;
            U[i] = D[i] = i;
            S[i] = 0;
        }
        L[0] = m;
        R[m] = 0;
        int id = m+1;
        for(int i=1; i<=n; i++)
        {
            int num;
            scanf("%d", &num);
            H[i] = -1;
            while(num--)
            {
                int col;
                scanf("%d", &col);
                link(i, col, id++);
            }
        }
        if(!dance(0))
            printf("NO\n");
    }
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值