让咱们看看百度百科上的解释:在 计算机科学 中, Dancing Links ,舞蹈链, 也叫 DLX, 是由 Donald Knuth 提出的数据结构,目的是快速实现他的 X算法.X算法是一种递归算法,时间复杂度不肯定, 深度优先, 经过回溯寻找精确覆盖问题全部可能的解。有一些著名的精确覆盖问题,包括铺砖块,八皇后问题,数独问题。html
X算法用由0和1组成的矩阵A来表示精确覆盖问题,目标是选出矩阵的若干行,使得其中的1在全部列中出现且仅出现一次。(出自度娘)
算法
例若有一个这样的矩阵A:
\[ \mathbf{A} = \left( \begin{array}{ccc} {0} & {0} & {1} & {0} & {1} & {1} & {0} \\ {1} & {0} & {0} & {1} & {0} & {0} & {1} \\ {0} & {1} & {1} & {0} & {0} & {1} & {0} \\ {1} & {0} & {0} & {1} & {0} & {0} & {0} \\ {0} & {1} & {0} & {0} & {0} & {0} & {1} \\ {0} & {0} & {0} & {1} & {1} & {0} & {1} \\ \end{array} \right) \]
ps:例子引用自grenet奆佬
这个例子就包含了一个(1,4,5)的精确覆盖解。数组
1.而后让咱们人工模拟一遍X算法,好好体会体会:
最开始首先假定选择第一列:
\[ \left( \begin{array}{ccc} {0} & {0} & {1} & {0} & {1} & {1} & {0} \\ { } & { } & { } & { } & { } & { } & { } \\ { } & { } & { } & { } & { } & { } & { } \\ { } & { } & { } & { } & { } & { } & { } \\ { } & { } & { } & { } & { } & { } & { } \\ { } & { } & { } & { } & { } & { } & { } \\ \end{array} \right) \]
那么对于第一行有1的列,即(3,5,6),可向下不断延伸,遇到有1的位置,就把该行标记:
\[ \mathbf{B} = \left( \begin{array}{ccc} {0} & {0} & {1} & {0} & {1} & {1} & {0} \\ { } & { } & {0} & { } & {0} & {0} & { } \\ {0} & {1} & {1} & {0} & {0} & {1} & {0} \\ { } & { } & {0} & { } & {0} & {0} & { } \\ { } & { } & {0} & { } & {0} & {0} & { } \\ {0} & {0} & {0} & {1} & {1} & {0} & {1} \\ \end{array} \right) \]数据结构
2.这样就能够用A矩阵-B矩阵(即删除所标记的行和列),获得一个新的、小一点的矩阵A(即获得一个规模较小的精确覆盖问题):
\[ \mathbf{A} = \left( \begin{array}{ccc} {1} & {0} & {1} & {1} \\ {1} & {0} & {1} & {0} \\ {0} & {1} & {0} & {1} \\ \end{array} \right) \]优化
3.那么根据1,又可进行一下操做:
先选第一列:
\[ \mathbf{A} = \left( \begin{array}{ccc} {1} & {0} & {1} & {1} \\ { } & { } & { } & { } \\ { } & { } & { } & { } \\ \end{array} \right) \]
那么对于第一行有1的列,即(1,3,4),可向下不断延伸,遇到有1的位置,就把该行标记:
\[ \mathbf{B} = \left( \begin{array}{ccc} {1} & {0} & {1} & {1} \\ {1} & { } & {1} & {0} \\ {0} & {1} & {0} & {1} \\ \end{array} \right) \]spa
4.这样就能够用A矩阵-B矩阵(即删除所标记的行和列),又获得一个新的、小一点的矩阵A(即又获得一个规模较小的精确覆盖问题):
\[ \mathbf{A} = \left( \begin{array}{ccc} {0}\\ \end{array} \right) \]
这个时候咱们发现当前A矩阵不为空,也没有一列有1(既没法继续操做)
则这个时候说明以前走出了错误的一步,就须要咱们回溯——指针
5.那么根据步骤就回溯到3:
\[ \mathbf{A} = \left( \begin{array}{ccc} {1} & {0} & {1} & {1} \\ {1} & {0} & {1} & {0} \\ {0} & {1} & {0} & {1} \\ \end{array} \right) \]
这个时候就不能尝试第一行,那咱们就标记第二行,并按照以前的方法扩展:
\[ \mathbf{B} = \left( \begin{array}{ccc} {1} & {0} & {1} & {1} \\ {1} & {0} & {1} & {0} \\ {0} & { } & {0} & { } \\ \end{array} \right) \]code
6.那么由4,同理咱们可得:
\[ \mathbf{A} = \left( \begin{array}{ccc} {1} & {1} \\ \end{array} \right) \]
那么继续上面的步骤,显然整个矩阵最后就能够被缩减完。htm
7.由此,咱们就获得了那组解(1,4,5)blog
根据咱们刚才的运行过程,
若是过程当中有大量的回溯和标记过程,那咱们若是用数组存储以前的信息,显然是不可能的,~妥妥的MLE没商量
这个时候咱们就要隆重请出舞蹈链的X算法(即DLX)
1.那么最开始初始化的时候,咱们将第一行的指针指好:
template<typename TP>inline void init(TP n,TP m) { F1(i,0,m) { L[i]=i-1,R[i]=i+1; U[i]=D[i]=i; } L[0]=m,R[m]=0,cnt=m; //cnt即已有节点,H[]即链表表头 memset(H,-1,sizeof H); return; }
2.对输入矩阵扫描,对于有1的点进行插入操做:
这样咱们就只在1与1之间建链,对于X算法中挨个挨个去扩展来找1就要快得多:
template<typename TP>inline void push(TP r,TP c) { U[++cnt]=c,D[cnt]=D[c]; U[D[c]]=cnt,D[c]=cnt; row[cnt]=r,col[cnt]=c; if(H[r]!=-1) { R[cnt]=R[H[r]],L[R[H[r]]]=cnt; L[cnt]=H[r],R[H[r]]=cnt; } else H[r]=L[cnt]=R[cnt]=cnt; return; }
3.对于最关键的删除/回溯操做
由于只将有1的点加入链表,因此直接扫就行
值得一提的是,咱们在删除的时候(就是从当前所选列扩展的时候),咱们只是把"对应列"有1的行与整个链表“分开”,而这一行元素之间的关系并无破坏,这样回溯的时候就至关容易,只需反着操做一遍便可。
代码以下:
//删除操做: template<typename TP>inline void del(TP c) { L[R[c]]=L[c],R[L[c]]=R[c]; for(TP i=D[c];i!=c;i=D[i]) for(TP j=R[i];j!=i;j=R[j]) U[D[j]]=U[j],D[U[j]]=D[j]; return; } //回溯操做: template<typename TP>inline void reback(TP c) { for(TP i=U[c];i!=c;i=U[i]) for(TP j=L[i];j!=i;j=L[j]) U[D[j]]=D[U[j]]=j; L[R[c]]=R[L[c]]=c; return; }
4.接下来就是整个舞蹈链过程当中最美的地方:
template<typename TP>inline bool dancing(TP dep) { if(R[0]==0) { tot=dep; return true; } TP c=R[0];del(c); for(TP i=D[c];i!=c;i=D[i]) { ans[dep]=row[i];//记录答案 for(TP j=R[i];j!=i;j=R[j]) del(col[j]); if(dancing(dep+1)) return true; for(TP j=L[i];j!=i;j=L[j]) ret(col[j]); } ret(c); //这个地方必定要记得回溯!! return false; }
LuoguP4929 【模板】舞蹈链(DLX)
完整代码(建议先本身写一遍再看):
#include<cstdio> #include<cstring> #define rg register int #define I inline int #define V inline void #define ll long long #define db double #define B inline bool #define F1(i,a,b) for(rg i=a;i<=b;++i) #define F2(i,a,b) for(rg i=a;i>=b;--i) #define ed putchar('\n') #define bl putchar(' ') using namespace std; #define getchar()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) char buf[1<<21],*p1=buf,*p2=buf; template<typename TP>V read(TP &x) { TP f=1;x=0;register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') f=-1; for(;c>='0'&&c<='9';c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); x*=f; } template<typename TP>V print(TP x) { if(x<0) x=-x,putchar('-'); if(x>9) print(x/10); putchar(x%10+'0'); } const int N=250005; int n,m,a,cnt; struct Dancing_Links_X{ int U[N],D[N],L[N],R[N],col[N],row[N],ans[N],H[N]; V init() { F1(i,0,m) { L[i]=i-1,R[i]=i+1; U[i]=D[i]=i; } L[0]=m,R[m]=0,cnt=m; memset(H,-1,sizeof H); return; } template<typename TP>V push(TP r,TP c) { U[++cnt]=c,D[cnt]=D[c]; U[D[c]]=cnt,D[c]=cnt; row[cnt]=r,col[cnt]=c; if(H[r]!=-1) { L[cnt]=H[r],R[cnt]=R[H[r]]; L[R[H[r]]]=cnt,R[H[r]]=cnt; } else H[r]=L[cnt]=R[cnt]=cnt; return; } template<typename TP>V del(TP c) { L[R[c]]=L[c],R[L[c]]=R[c]; for(TP i=D[c];i!=c;i=D[i]) for(TP j=R[i];j!=i;j=R[j]) U[D[j]]=U[j],D[U[j]]=D[j]; return; } template<typename TP>V reback(TP c) { for(TP i=U[c];i!=c;i=U[i]) for(TP j=L[i];j!=i;j=L[j]) U[D[j]]=D[U[j]]=j; L[R[c]]=R[L[c]]=c; return; } template<typename TP>B dancing(TP tot) { if(R[0]==0) { F1(i,0,tot-1) print(ans[i]),bl; return true; } TP c=R[0];del(c); for(TP i=D[c];i!=c;i=D[i]) { ans[tot]=row[i]; for(TP j=R[i];j!=i;j=R[j]) del(col[j]); if(dancing(tot+1)) return true; for(TP j=L[i];j!=i;j=L[j]) reback(col[j]); } reback(c); return false; } }DLX; int main() { read(n),read(m),DLX.init(); F1(i,1,n) F1(j,1,m) { read(a); if(a) DLX.push(i,j); } if(!DLX.work(0)) puts("No Solution!"); return 0; }
固然,若是你彻底按照上面这么打必定会~TLE(hhh)
这个地方还有一个优化,就是咱们在"dancing"过程当中,不管用什么方法选择列最终均可以获得解,但有的方法效率明显较高。
为减小迭代次数,咱们能够每次都选取1最少的列。
进行这个操做咱们只需再定义一个数组s[]
在"push“中加上这样一句:
++s[c];
将"del"部分改为:
U[D[j]]=U[j],D[U[j]]=D[j],--s[col[j]];
将"reback"部分改为:
U[D[j]]=D[U[j]]=j,++s[col[j]];
将"dancing"部分改为:
TP c=R[0]; for(TP i=c;i!=0;i=R[i]) if(s[i]<s[c]) c=i; del(c);