舞蹈链DLX

算法

算法应用

主要用于精准覆盖问题,通过一点简单的改变就可以直接求重复覆盖问题。

算法流程

  1. 对于当前的矩阵,选择一行,将这一行和不能与这条边一起选的边上的所有点删掉。(不能一起选就是在同一列有点)
  2. 如果删掉后所有列都被删完了,就结束。
  3. 如果这时候还有列没被删,但没有行可以选了就说明不合法,开始回溯,将删去的边加回来。

总结:就是爆搜选的边,但是有一些优化剪枝,不能选矛盾的边。(看到这你会有一种云里雾里的感觉,不明白怎么快速实现,这是正常的,你只需要理解上面是正确的即可)

链表

上面的算法需要我们快速实现删除,回溯以及一些查询。我们考虑到链表是一种支持快速修改的结构,同时易于回溯(因为链表没有真正删除点,只是不经过它而已)。所以用链表实现上述操作。
所以我们引入十字交叉双向循环链表 ,看起来很高级,其实很简单,十字交叉是因为在两条链上,一条是行一条是列。双向就不用说了,循环是为了方便查找。(它就长下面这样子,图来自oi-wiki)
image
将一的信息存下来,为了方便寻找每一列的信息再多存一下列的编号。

代码实现

预处理&建模

void ycl()
{
    for(int i=0;i<=m;i++)
    {
        dian[i].l=i-1;dian[i].r=i+1;
        dian[i].u=i;dian[i].d=i;//有用,竖着的链也要首尾相连
        lcnt[i]=0;//记录每列的1的个数
    }
    cnt=m;
    dian[0].l=m,dian[cnt].r=0;//形成环
    return ;
}
void addnode(int x,int y)
{
    dian[++cnt].row=x;dian[cnt].col=y;//行列信息
    dian[cnt].u=dian[y].u;dian[dian[y].u].d=cnt;
    dian[cnt].d=y;dian[y].u=cnt;
    if(!bk[x])//这一行的第一个1,bk[x]记录了第一个1的编号
    {
        dian[cnt].l=dian[cnt].r=cnt;//左右都没有点,自己成环
        bk[x]=cnt;
    }
    else
    {
        dian[cnt].l=dian[bk[x]].l;dian[dian[bk[x]].l].r=cnt;
        dian[cnt].r=bk[x];dian[bk[x]].l=cnt;
    }
    lcnt[y]++;//记录每列的1的个数
}

删除某一列及相关的东西

void del(int x)
{
    for(int i=dian[x].d;i!=x;i=dian[i].d)//枚举在这一列是一的行,都要删除
    {
        for(int j=dian[i].r;j!=i;j=dian[j].r)//枚举删除的行上的列
        {
            dian[dian[j].u].d=dian[j].d;
            dian[dian[j].d].u=dian[j].u;
            lcnt[dian[j].col]--;
        }
    }
    dian[dian[x].l].r=dian[x].r;
    dian[dian[x].r].l=dian[x].l;//特殊处理列的编号所在的链
    return ;
}

恢复某一列及相关的东西

与删除相反即可

void rev(int x)
{
    dian[dian[x].l].r=x;
    dian[dian[x].r].l=x;
    for(int i=dian[x].d;i!=x;i=dian[i].d)//枚举删除的行
    {
        for(int j=dian[i].r;j!=i;j=dian[j].r)//枚举删除的行上的列
        {
            dian[dian[j].u].d=j;
            dian[dian[j].d].u=j;
            lcnt[dian[j].col]++;
        }
    }
    return ;
}

dance!!!

bool dance(int dep)//搜索的层数
{
    if(dian[0].r==0)//所有列都被合法删除,有解
    {
        for(int i=1;i<dep;i++)cout<<ans[i]<<' ';
        cout<<'\n';
        return 1;
    }
    int c=dian[0].r;
    for(int i=c;i;i=dian[i].r)//在列的那条链上搜索
        if(lcnt[i]<lcnt[c])c=i;//找最小的列,剪枝
    del(c);//删除列c
    for(int i=dian[c].d;i!=c;i=dian[i].d)//枚举选择的行
    {
        ans[dep]=dian[i].row;
        for(int j=dian[i].r;j!=i;j=dian[j].r)//枚举删除的行上的列
            del(dian[j].col);
        if(dance(dep+1))return 1;          
        for(int j=dian[i].r;j!=i;j=dian[j].r)//恢复删除的行上的列
            rev(dian[j].col);
    }
    rev(c);
    return 0;
}

总代码

#include <bits/stdc++.h>
using namespace std;
const int N=50010;
int n,m,cnt,lcnt[N],bk[N],ans[N];
struct node
{
    int l,r,u,d,row,col;//l,r,u,d分别表示左右上下节点编号,row,col表示所在行列
}dian[N];
void ycl()
{
    for(int i=0;i<=m;i++)
    {
        dian[i].l=i-1;dian[i].r=i+1;
        dian[i].u=i;dian[i].d=i;//是否有用?
        lcnt[i]=0;//记录每列的1的个数
    }
    cnt=m;
    dian[0].l=m,dian[cnt].r=0;//形成环
    return ;
}
void addnode(int x,int y)
{
    dian[++cnt].row=x;dian[cnt].col=y;
    dian[cnt].u=dian[y].u;dian[dian[y].u].d=cnt;
    dian[cnt].d=y;dian[y].u=cnt;
    if(!bk[x])//这一行的第一个1,bk[x]记录了第一个1的编号
    {
        dian[cnt].l=dian[cnt].r=cnt;//左右都没有点,自己成环
        bk[x]=cnt;
    }
    else
    {
        dian[cnt].l=dian[bk[x]].l;dian[dian[bk[x]].l].r=cnt;
        dian[cnt].r=bk[x];dian[bk[x]].l=cnt;
    }
    lcnt[y]++;//记录每列的1的个数
}
void del(int x)
{
    for(int i=dian[x].d;i!=x;i=dian[i].d)//枚举删除的行
    {
        for(int j=dian[i].r;j!=i;j=dian[j].r)//枚举删除的行上的列
        {
            dian[dian[j].u].d=dian[j].d;
            dian[dian[j].d].u=dian[j].u;
            lcnt[dian[j].col]--;
        }
    }
    dian[dian[x].l].r=dian[x].r;
    dian[dian[x].r].l=dian[x].l;
    return ;
}
void rev(int x)
{
    dian[dian[x].l].r=x;
    dian[dian[x].r].l=x;
    for(int i=dian[x].d;i!=x;i=dian[i].d)//枚举删除的行
    {
        for(int j=dian[i].r;j!=i;j=dian[j].r)//枚举删除的行上的列
        {
            dian[dian[j].u].d=j;
            dian[dian[j].d].u=j;
            lcnt[dian[j].col]++;
        }
    }
    return ;
}
bool dance(int dep)//搜索的层数
{
    if(dian[0].r==0)//所有列都被合法删除,有解
    {
        for(int i=1;i<dep;i++)cout<<ans[i]<<' ';
        cout<<'\n';
        return 1;
    }
    int c=dian[0].r;
    for(int i=c;i;i=dian[i].r)//在列的那条链上搜索
        if(lcnt[i]<lcnt[c])c=i;//找最小的列
    del(c);//删除列c
    for(int i=dian[c].d;i!=c;i=dian[i].d)//枚举删除的行
    {
        ans[dep]=dian[i].row;
        for(int j=dian[i].r;j!=i;j=dian[j].r)//枚举删除的行上的列
            del(dian[j].col);
        if(dance(dep+1))return 1;          
        for(int j=dian[i].r;j!=i;j=dian[j].r)//恢复删除的行上的列
            rev(dian[j].col);
    }
    rev(c);
    return 0;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>n>>m;
    ycl();//处理head和列号
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            int x;cin>>x;
            if(x==1)addnode(i,j);//建节点
        }
    
    if(!dance(1))cout<<"No Solution!"<<'\n';
    return 0;
}

应用

posted @ 2024-12-06 21:19  storms11  阅读(2)  评论(0编辑  收藏  举报