DLX 简介(hihocoder1317)

问题简介

精确覆盖问题:给定一个01矩阵,从中选出若干行,使得每一列恰好有一个1。求一个方案。

算法实现

搜索

每次选择一行,然后把这一行有1的列,在同一列中也有1的其它行,以及这一行本身从矩阵中除去。然后又变成了一个小的矩阵,一直递归至只剩一行。如果这一行全是1,则返回有解并输出选择的行编号,否则无解并恢复原来矩阵。如果搜索完毕后仍没有方案则无解。

下面给出一张大矩阵变小矩阵的图:
这里写图片描述

其中红圈圈的表示被除去的元素。(第1,2,5行、第1,2,4列)

至于方案每次记录当前选的是哪一行就好了。

DLX

我们可以发现:之前的搜索需要耗费大量的空间来存储每一个矩阵,而且还原起来非常麻烦,而DLX可以完美解决这个问题。它的空间是 O(n2) O ( n 2 ) 的,而且时间也非常快。也就是说,它根本不需要额外的空间来存储。

DLX在hihocoder1317的提示中写的非常详细,这里因为实在太懒不再赘述,只对几个重要的过程做简要说明。

Build()

Build()操作即把原来的矩阵加到DLX中。

刚开始把列头节点建好,再分别从横向、纵向两个方向指好各个指针。

inline void build(){
    h->d=h->u=h->l=h->r=h,h->x=h->y=0,pre=h;//建head
    for (int i=1;i<=m;i++){//建列头节点
        p=&a[i]; p->u=p->d=p,p->x=0,p->y=i;
        p->r=pre->r,p->l=pre,pre->r->l=p,pre->r=p,pre=p;
    }
    nd=0;
    for (int i=1;i<=n;i++)//标号
        for (int j=1;j<=m;j++)
            if (mp[i][j]){
                id[i][j]=++nd,c[nd].d=c[nd].l=&c[nd];
                c[nd].r=c[nd].u=&c[nd],c[nd].x=i,c[nd].y=j;
            }
    for (int j=1;j<=m;j++){//纵向添加
        pre=&a[j];
        for (int i=1;i<=n;i++)
            if (mp[i][j]){
                nod *p=&c[id[i][j]]; p->d=pre->d,p->u=pre;
                pre->d->u=p,pre->d=p,pre=p;
            }
    }
    for (int i=1;i<=n;i++){//横向添加
        pre=null;
        for (int j=1;j<=m;j++)
            if (mp[i][j])
                if (pre==null) pre=&c[id[i][j]];
                else{
                    p=&c[id[i][j]],p->r=pre->r,p->l=pre;
                    pre->r->l=p,pre->r=p,pre=p;
                }
    }
}

remove(x)

移除第x列。

首先将第x列的左右两列指针互指,然后不断往下让左右列元素互指,直到该列头节点d指针指向自己。

inline void rmv(int x){
    nod *p=&a[x],*p1=p->d;
    p->r->l=p->l,p->l->r=p->r;
    while (p1!=p){//直到该列头节点d指针指向自己
        nod *p2=p1->r;
        while (p2!=p1)
            p2->d->u=p2->u,p2->u->d=p2->d,p2=p2->r;
        p1=p1->d;
    }
}

resume(x)

恢复第x列。

和remove(x)相反即可。

inline void rsm(int x){
    nod *p=&a[x],*p1=p->d;
    p->r->l=p->l->r=p;
    while (p1!=p){
        nod *p2=p1->r;
        while (p2!=p1)
            p2->d->u=p2->u->d=p2,p2=p2->r;
        p1=p1->d;
    }
}

dfs(x)

主体过程。

首先判是否已经有解。
若h的右指针不是自己那么删除h的右指针指向的那一列,并枚举每一行(删除,判解(继续递归),恢复),恢复该列并返回无解。

bool dfs(int x){
    nod *p=h->r,*p1=p->d;
    if (p==h) return true;
    if (p1==p) return false;
    rmv(p->y);
    while (p1!=p){
        ans[x]=p1->x; nod *p2=p1->r;
        while (p2!=p1) rmv(p2->y),p2=p2->r;
        if (dfs(x+1)) return true;
        p2=p1->l;
        while (p2!=p1) rsm(p2->y),p2=p2->l;
        p1=p1->d;
    }
    return rsm(p->y),false;
}

算法用途

DLX可以求数独。
戳这里
其他的暂时不清楚~~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值