Dancing Links(舞蹈链)

版权声明:作为一个蒟蒻,转载时请通知我这个蒟蒻 https://blog.csdn.net/zyszlb2003/article/details/89318336

首先
舞蹈链(简称DLX,下文均用DLX表示舞蹈链),是一种数据结构,并不是算法,NOIP不经常考,但对于精确覆盖问题,例如LuoguP4929具有优秀的时间复杂度和愉快的AC感觉
关于具体的模拟过程,请参考跳跃的舞者,舞蹈链(Dancing Links)算法——求解精确覆盖问题
本文主要提供

  1. c++的DLX模版

  2. 就模版中一些数组的理解

  3. 对实际应用的难点

    1.c++的DLX模版(LuoguP4929)(代码风格较丑,见谅见谅)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
using namespace std;
const int N=5510;
const int M=510;
int r[N],l[N],u[N],d[N],col[N],row[N],sz[M],head,num;
void remove(int p)
{
    r[l[p]]=r[p];l[r[p]]=l[p];
    for(int i=u[p];i!=p;i=u[i])
        for(int j=r[i];j!=i;j=r[j])
            u[d[j]]=u[j],d[u[j]]=d[j],--sz[col[j]];
}
void resume(int p)
{
    r[l[p]]=l[r[p]]=p;
    for(int i=u[p];i!=p;i=u[i])
        for(int j=r[i];j!=i;j=r[j])
            u[d[j]]=d[u[j]]=j,++sz[col[j]];
}
void link(int i,int j)
{
    ++num;
    u[num]=u[j];d[num]=j;col[num]=j;row[num]=i;
    u[d[num]]=d[u[num]]=num;++sz[j];
    if(!head)head=num;
    else l[num]=num-1,r[num-1]=num;
}
bool bk;
void dance()
{
    if(!r[0]){bk=true;return ;}
    int p=r[0];
    for(int i=r[p];i;i=r[i])if(sz[i]<sz[p])p=i;
    remove(p);
    for(int i=d[p];i!=p;i=d[i])
    {
        for(int j=r[i];j!=i;j=r[j])remove(col[j]);
        dance();if(bk){printf("%d ",row[i]);return ;}
        for(int j=r[i];j!=i;j=r[j])resume(col[j]);
    }
    resume(p);
}
int main()
{
    int n,m;scanf("%d%d",&n,&m);
    for(int i=0;i<=m;i++)l[i]=i-1,r[i]=i+1,u[i]=d[i]=i;
    l[0]=m;r[m]=0;num=m;
    for(int i=1;i<=n;i++)
    {	
        for(int j=1;j<=m;j++)
        {
            int x;scanf("%d",&x);
            if(x)link(i,j);
        }
        if(head)l[head]=num,r[num]=head,head=0;
    }
    dance();
    if(!bk)puts("No Solution!");
    return 0;
}

2.就模版中一些数组的理解

DLX实际上是一个双向十字链表。
r[N],l[N]:
0~n 
	实际上编译时会改变的范围,也就是上文提到的链接中的列标C1,C2,......,Cn,之后要形成一个
	闭间,即为0~n.
	int p=r[0];
	for(int i=r[p];i;i=r[i])if(sz[i]<sz[p])p=i;这里即用到的是r[0~n]区间的
	r[l[p]]=r[p];l[r[p]]=l[p];暂时隐去p这一列(以后可能会回溯,就是下面那一行)
	r[l[p]]=l[r[p]]=p;
n+1~num 
	实际上指的是某一行中的‘1’的位置,每一行都会形成一个闭区间,即
	if(head)l[head]=num,r[num]=head,head=0;
u[N],d[N]:
0~n 
	分别指向列表自身的数值,也是引出具体‘1’的闭区间开头,不会改变。
n+1~num
	形成一个列的链表,自身处在j列,即由u[j]引出链表。

模拟列的链表如何形成
u [ j ] = d [ j ] = j u[j]=d[j]=j ;插入 n u m 1 , u [ n u m 1 ] = u [ j ] = j , d [ n u m 1 ] = j , u [ d [ n u m 1 ] ] = d [ u [ n u m 1 ] ] = n u m 1 num_1,u[num_1]=u[j]=j,d[num_1]=j,u[d[num_1]]=d[u[num_1]]=num_1 ,即构成一个 j n u m 1 j\leftrightarrow num_1 这样一个闭区间.
再插入 n u m 2 , u [ n u m 2 ] = u [ j ] = n u m 1 , d [ n u m 2 ] = j ; u [ d [ n u m 2 ] ] = u [ j ] = d [ u [ n u m 2 ] ] = d [ n u m 1 ] = n u m 2 num_2,u[num_2]=u[j]=num_1,d[num_2]=j;u[d[num_2]]=u[j]=d[u[num_2]]=d[num_1]=num_2
形成 j n u m 1 n u m 2 j j\leftrightarrow num_1 \leftrightarrow num_2 \leftrightarrow j 这样一个闭区间
以此类推即可。
下来就是找size最小的列(因为这样可以使状态变少),
然后找到一个‘1’的位置,标记这一行,
找出被标记‘1’的位置,后将被标记这一列,
标记这一列中‘1’所处的位置,隐去‘1’所在行的所有‘1‘的标识。
再执行一次dance
如果不行,则回溯。

3.对于实际应用的难点

DLX主要难在于如何构图,以及如何记录答案
因为很少会有裸的精确覆盖或者裸的重复覆盖。
因此推荐几道题,用DLX做
数独三连POJ2676POJ3074POJ3076题解传送门
靶形数独 NOIP提高组第四题题解传送门

猜你喜欢

转载自blog.csdn.net/zyszlb2003/article/details/89318336