Dancing Links X专题学习(代码版)

Dancing Links X专题学习

写在前面:本人萌新,这篇博文是建立在众位大佬的博客之上,然后在自己学习的过程中加入自我分析。欢迎吐槽!

关键词:递归 | 回溯 | 深度优先 | 非确定性的
应用:精确覆盖 | 数独 | 重复覆盖 | 等

一 . 精确覆盖

【引例】Girl Match
在一个寒冷的冬天,卖女孩的小火柴 yy 来到了一家奇特的餐馆 ——Match Girl。该餐馆中有 N 种食品(食品按 1~N 编号)。在这个奇怪的餐馆里,食品都按套餐出售。已知有 M 种套餐,对于每一种套餐,给出该套餐包含的食品编号(保证不存在两个完全相同的套餐)。yy 饿极了,她希望能够品尝到每一种食品。但 yy 由于没有能够凭借美色拐到足够的女孩,现在穷得响叮当。她希望能够购买到所有的食品且不重复购买同一种食品。她已经饿得没有力气继续思考了,而你是她拐回来的唯一的“女孩”。作为回报,她不会把你卖了。她能够实现这小小的愿望吗?如果能,输出"Yes";否则,输出"No"。

现有 6 种食品,5 种套餐:
套餐 1 :1 3
套餐 2 :1 3 4 6
套餐 3 :2 5
套餐 4 :3 4 5 6
套餐 5 :1 2 3 4 5
答案为:Yes(选择套餐2和套餐3)

穷举、状压等方法都可以完成 N、M 较小的部分数据,这里不再一一赘述。直接引入 Dancing Links X 的思考过程。

将套餐和对应的食品编号转化成0/1矩阵,如下图,第 i 行代表第 i 个套餐对应的食品编号(1为包含该编号的食品)。我们要解决的问题就成了选取一个行集合,使得选出集合后所得到矩形的每一列上有且仅有一个1。

1 0 1 0 0 0 —— 第一行
1 0 1 1 0 1 —— 第二行
0 1 0 0 1 0 —— 第三行
0 0 1 1 1 1 —— 第四行
1 1 1 1 1 0 —— 第五行

这就是经典的精确覆盖问题。更常见的问题,需要同时保证所选的行数最少,并输出方案。

再回到这个问题。

对于该0/1矩阵,首先假定我们选择了第一行。那么第一行以及与第一行有冲突的行都没有可能在后面选择到(*有"冲突"表示的是:该行与其它行存在共同的都为 1 的列)。所以,我们可以将与第一行有冲突的行都删除掉。同时,在以后选择的行中不再可能存在与第一行所有共同为 1 的列,将第一行对应为 1 的列也对应删除掉,如 图1-1 所示:

enter image description here

同样,再进行删除,将得到一个空矩阵。由此,我们可以发现:我们在原矩阵中选择了 2 行,但只选择了 4列(共 6 列)。所以,该选择方案不成立,那么不断回溯,重新进行选择。由于最后的选择只有一种,那么直接回溯到第一次的选择。

选择删除第二行,如图 1-2 :

enter image description here

再进行删除。此次操作,我们在原矩阵中选择了 2 行,对应选择了 6 列(共 6 列)。得到答案。

在这个搜索过程中,我们调用了大量矩阵缓存和矩阵回溯。如果用传统的矩阵存储,每次的删除,我们最坏的时间复杂度为O( NM )。如果该方案搜索失败,又需要用O( NM )的复杂度回溯。这种操作的时间复杂度难以解决。

于是,由算法大师Donald Knuth提出了"X算法"。这是一种递归的、非确定的、深度优先的回溯算法。Dancing Links,即舞蹈链,本身是一种链式的数据结构。利用链表的性质,不再在删除操作中开辟更多的空间,以O(1)的时间实现删除列,以<=O(N)的时间删除行。也是这样,Dancing Links由此得名。而"Dancing Links X"的含义正是利用"舞蹈链"来求解"X算法"的意思。(*本段转载非原创,详见 英雄哪里出来 文章第三部分Dancing Links X算法 第 4 点Dancing Links

假设我们现在 A 以链表的形式存储,在链表的更改操作如下:

A[A[i].Left].Right->A[i].Left
A[A[i].Right].Left->A[i].Right

我们可以发现:A[i].Left 和 A[i].Right 的指向都发生了变化,但是 A[i] 本身没有,如果我们想要将 A[i] 重新放入,只需要更改为

A[A[i].Left].Right->i
A[A[i].Right].Left->i

即可。

Dancing Links其实是一种十字交叉双向循环链表。依靠链表的这一性质,快速地将某一列或某一行先移除,然后在更改回来。Dancing Links的节点可以分为以下四类。

  1. 总表头:连接行首与列首节点。
  2. 列首节点
  3. 行首节点:可以看做是一个指针数组,Row[i]记录了第 i 行第一个节点的编号。
  4. 元素节点

如 图1-3 所示:

enter image description here

(* 注明:行首节点 Row 由于是单向不再在图中画出)

算法的流程如下(译自维基百科):

  1. 判断矩阵是否为空(行) 。行为空且列为空,记录答案并终止查询;列不为空,返回上层并继续查询。否则,继续往后。

  2. 选择列 c (确定性选择)

  3. 选择一行 r,使得 A[r,c]=1 (非确定性选择)

  4. 将 r 统计进临时答案变量中

  5. 枚举每列 j 使得 A[r,j]=1;
    枚举每行 k 使得 A[k,j]=1;
    从矩阵中删除第 i 行

  6. 在简化矩阵中重复算法

用 引例 中给出的数据,模拟过程为:

移除第一行以及与它冲突的。

enter image description here

继续移除:

enter image description here

发现矩阵不为空(依旧存在列首节点),没有找到答案。

回溯到 图1-6 所示:

enter image description here

回溯到 图1-7 所示:

enter image description here

移除第二行以及与它冲突的:

enter image description here

继续移除:

enter image description here

矩阵为空,找到答案。

再做 Dancing Links 的过程中,每次寻找哪一列的 1 个数最少。然后枚举该列为 1 的行,选择删除。这样能够较大幅度加快寻找的速度。

预处理:

void Ready(){
    	for(int i=0;i<=C;i++){// 0 为 Head 
    	    Sum[i]=0;
    		Node[i].Up=i;
    		Node[i].Down=i;
    		Node[i].Left=i-1;
    		Node[i].Right=i+1;
    		Node[i].Col=i;
    		Node[i].Row=0;
    	}//第一行(列首节点)初始化 
    	Node[0].Left=C;
    	Node[C].Right=0;
    	cnt=C;
    	for(int i=1;i<=R;i++) Row[i]=0;//行首节点初始化
    }

加入元素节点:

void Push(int x,int y){
	cnt++;//新建一个点
	Sum[y]++;//统计该列节点数
	Node[cnt].Down=Node[y].Down;//将当前元素放在第 y 列的第一个 
	Node[cnt].Up=y;
	Node[Node[y].Down].Up=cnt;
	Node[y].Down=cnt;
	Node[cnt].Row=x;
	Node[cnt].Col=y; 
	if(!Row[x]){
		Row[x]=cnt;
		Node[cnt].Left=cnt;
		Node[cnt].Right=cnt;//当前行没有节点 
	}else{
		Node[cnt].Left=Row[x];
		Node[cnt].Right=Node[Row[x]].Right;
		Node[Node[Row[x]].Right].Left=cnt;
		Node[Row[x]].Right=cnt;//将新建节点放在该行的第一个节点的后面
	}
}//节点插入的写法有很多

删除操作:


    void Delete(int c){//选中第 c 列 
	Node[Node[c].Left].Right=Node[c].Right;
	Node[Node[c].Right].Left=Node[c].Left;
	for(int i=Node[c].Down;i!=c;i=Node[i].Down)
	 for(int j=Node[i].Right;j!=i;j=Node[j].Right){
	 	Node[Node[j].Up].Down=Node[j].Down;
	 	Node[Node[j].Down].Up=Node[j].Up;
	 	Sum[Node[j].Col]--;
	 }//删除该列所有 1 所在行的所有数 
  } 

回溯:


    void Return(int c){
	for(int i=Node[c].Up;i!=c;i=Node[i].Up)
	 for(int j=Node[i].Left;j!=i;j=Node[j].Left){
	 	Node[Node[j].Up].Down=j;
	 	Node[Node[j].Down].Up=j;
	 	Sum[Node[j].Col]++;
	 }
    }

舞蹈:

  bool Dancing(int Dep){
  if(!Node[0].Right){
  	N=Dep;
      return true;
  }//矩阵是否为空
  int c=Node[0].Right;
  for(int i=Node[c].Right;i;i=Node[i].Right){
  	if(Sum[i]<Sum[c])
  	 c=i;
  }//找最小
  Delete(c);
  for(int i=Node[c].Down;i!=c;i=Node[i].Down){
  	Ans[Dep]=Node[i].Row;//记录答案
  	for(int j=Node[i].Right;j!=i;j=Node[j].Right) Delete(Node[j].Col); 
  	if(Dancing(Dep+1)) return true;
  	for(int j=Node[i].Left;j!=i;j=Node[j].Left) Return(Node[j].Col);
  } 
  Return(c);
  return false;
}

*由于此题不存在,不附上完全代码

【例题1】Easy Finding

给定一个M*N(M<=16,N<=300)的0/1矩阵,寻找一些行,使得重新得到的矩阵的每一列上有且仅有一个1。如果有解,输出"Yes, I found it";否则,输出"It is impossible"。

Dancing Links X的模板题。按照描述的矩阵构图套上模板。

#include<bits/stdc++.h>
int R,C,cnt,N,a[510][1010]={},Row[510]={},Sum[1010]={};
struct Dancing_Link_X{
	int Up,Down,Left,Right;
   	int Col,Row;
} Node[500010]={};
void Ready(){
    for(int i=0;i<=C;i++){
  		Sum[i]=0;
   		Node[i].Up=i;
   		Node[i].Down=i;
   		Node[i].Left=i-1;
   		Node[i].Right=i+1;
		Node[i].Col=i;
    	Node[i].Row=0;
    }
    Node[0].Left=C;
    Node[C].Right=0;
    cnt=C;
    for(int i=1;i<=R;i++)
     Row[i]=-1;
}
void Push(int x,int y){
    Sum[y]++;
    cnt++;
    Node[cnt].Down=Node[y].Down;
    Node[cnt].Up=y;
   	Node[Node[y].Down].Up=cnt;
   	Node[y].Down=cnt;
   	Node[cnt].Row=x;
   	Node[cnt].Col=y; 
    if(Row[x]<0){
   		Row[x]=cnt;
   		Node[cnt].Left=cnt;
   		Node[cnt].Right=cnt;
   	}else{
   		Node[cnt].Left=Row[x];
    	Node[cnt].Right=Node[Row[x]].Right;
    	Node[Node[Row[x]].Right].Left=cnt;
    	Node[Row[x]].Right=cnt;
    }
}
void Delete(int c){
    Node[Node[c].Left].Right=Node[c].Right;
   	Node[Node[c].Right].Left=Node[c].Left;
   	for(int i=Node[c].Down;i!=c;i=Node[i].Down)
     for(int j=Node[i].Right;j!=i;j=Node[j].Right){
   	 	Node[Node[j].Up].Down=Node[j].Down;
   	 	Node[Node[j].Down].Up=Node[j].Up;
   	 	Sum[Node[j].Col]--;
       	 }
	} 
void Return(int c){
    for(int i=Node[c].Up;i!=c;i=Node[i].Up)
     for(int j=Node[i].Left;j!=i;j=Node[j].Left){
       	Node[Node[j].Up].Down=j;
      	Node[Node[j].Down].Up=j;
       	Sum[Node[j].Col]++;
    }
    Node[Node[c].Left].Right=c;
    Node[Node[c].Right].Left=c;
}     
bool Dancing(int Dep){
   if(!Node[0].Right) return true;
   int c=Node[0].Right;
    for(int i=Node[c].Right;i;i=Node[i].Right){
            if(Sum[i]<Sum[c])
             c=i;
    }
    Delete(c);
    for(int i=Node[c].Down;i!=c;i=Node[i].Down){
        for(int j=Node[i].Right;j!=i;j=Node[j].Right) Delete(Node[j].Col); 
        if(Dancing(Dep+1)) return true;
        for(int j=Node[i].Left;j!=i;j=Node[j].Left) Return(Node[j].Col);
        } 
        Return(c);
        return false;
    }
int main(){
    for(;scanf("%d%d",&R,&C)!=EOF;){
    	Ready();
    	for(int i=1;i<=R;i++)
    	 for(int j=1;j<=C;j++){
    	 	scanf("%d",&a[i][j]);
    	 	if(!a[i][j]) continue;
    		Push(i,j); 
    	}
    	if(Dancing(0))printf("Yes, I found it\n");
    	else printf("It is impossible\n");
    }
    return 0;
}

【例题2】Treasure Map
你的老板有许多同张藏宝图的副本。不幸的是,这些藏宝图的副本被打破成若干矩形的碎片。更糟糕的是,有些碎片丢失了。还好,可以得知每个碎片在藏宝图中的位置(给出一个范围,用x1,y1,x2,y2表示,x1<x2,y1<y2)现在老板要求你用这些碎片组成一张完整的藏宝图。(不必使用所有的碎片,但不允许重叠)。藏宝图的大小为N*M(N,M<=30),碎片数不超过300.(如图)

enter image description here

将N*M个格子的覆盖情况看作Dancing Links的列,每个小碎片看作行。将每个碎片所能覆盖的格子标为 1 ,作为元素节点加入。
细节的处理上,由于藏宝图的左下角由(0,0)开始,为了方便处理,将所有碎片的 x1、y1 都加上1。由于题目保证 x1<x2 且 y1<y2,碎片的相对位置没有改变,不会对答案造成影响。这张图就变为左下角由(1,1)开始的了。

#include<bits/stdc++.h>
using namespace std;
const int R=30*30,C=500;
int Ans,N,M,P,cnt,sum[R+10]={},Row[C+10]={};
struct Node{
	int L,R,U,D;
	int Row,Col;
} pnt[R*C+10]={};
void Ready(int r,int c){
	for(int i=0;i<=c;i++){
		pnt[i].U=pnt[i].D=i;
		pnt[i].L=i-1;
		pnt[i].R=i+1;
		pnt[i].Col=i;
		sum[i]=0;
	}
	pnt[0].L=c;
	pnt[c].R=0;
	cnt=c;
	for(int i=1;i<=r;i++) Row[i]=0;
}
inline void Add(int x,int y){
	cnt++; 
	sum[y]++;
	pnt[cnt].Row=x;
	pnt[cnt].Col=y;
	pnt[cnt].U=y;
	pnt[cnt].D=pnt[y].D;
	pnt[pnt[y].D].U=cnt;
	pnt[y].D=cnt;
	if(!Row[x]){
		Row[x]=cnt;
		pnt[cnt].L=pnt[cnt].R=cnt;
	}else{
		pnt[cnt].R=Row[x];
		pnt[cnt].L=pnt[Row[x]].L;
		pnt[pnt[Row[x]].L].R=cnt;
		pnt[Row[x]].L=cnt;
		Row[x]=cnt;
	}
}
inline void del(int c){
	pnt[pnt[c].L].R=pnt[c].R;
	pnt[pnt[c].R].L=pnt[c].L;
	for(int i=pnt[c].D;i!=c;i=pnt[i].D){
		for(int j=pnt[i].R;j!=i;j=pnt[j].R){
			pnt[pnt[j].U].D=pnt[j].D;
			pnt[pnt[j].D].U=pnt[j].U;
			sum[pnt[j].Col]--;
		}
	}
}
inline void ret(int c){
	for(int i=pnt[c].U;i!=c;i=pnt[i].U){
		for(int j=pnt[i].L;j!=i;j=pnt[j].L){
			pnt[pnt[j].U].D=pnt[pnt[j].D].U=j;
			sum[pnt[j].Col]++;
		}
	}
	pnt[pnt[c].L].R=pnt[pnt[c].R].L=c;
}
inline void Dancing(int Dep){
	if(Dep>=Ans&&Ans!=-1) return ;
	if(!pnt[0].R){
		if(Ans!=-1) Ans=min(Dep,Ans);
		else Ans=Dep;
		return ;
	}
	int c=pnt[0].R;
	for(int i=pnt[0].R;i;i=pnt[i].R){
		if(sum[i]<sum[c])
		 c=i;
	}
	del(c);
	for(int i=pnt[c].D;i!=c;i=pnt[i].D){
		for(int j=pnt[i].R;j!=i;j=pnt[j].R) del(pnt[j].Col);
		Dancing(Dep+1);
		for(int j=pnt[i].L;j!=i;j=pnt[j].L) ret(pnt[j].Col);
	}
	ret(c);
}
int main(){
	int T; scanf("%d",&T);
	for(;T--;){
		scanf("%d%d%d",&N,&M,&P);
		Ready(P,N*M);
		Ans=-1;
		for(int i=1;i<=P;i++){
			int x,y,X,Y;
			scanf("%d%d%d%d",&x,&y,&X,&Y);
			for(int j=x+1;j<=X;j++)
			 for(int k=y+1;k<=Y;k++)
			  Add(i,(j-1)*M+k);
		}
		Dancing(0);
		printf("%d\n",Ans);
	}
	return 0;
}

【例题3】Dominoes
需要用12种方块覆盖一个NM的棋盘,每个方块只能使用一次。求有多少种覆盖方案(方案翻转或旋转后相同算同一种方案。方块可以旋转,可以翻转)。N*M=60
enter image description here

由图可知,每个方块的大小为5,60的棋盘覆盖必定每一个方块都必须用上。且如果min(N,M) < 3,那么答案必定会为0。所以,60个格子的覆盖情况和12种方块的使用情况看作列,所有单个方块可能置的情况看作行。方块旋转和翻转后,不同的情况一共有63种,63种情况又可能放在棋盘中不同的位置(<63 * 60个),由这么多种情况看作行。直接做的话会超时。由于N*M=60,而60的因子十分少。我们可以用Dancing Links先处理处每一种答案,然后打表。
在细节的处理上,需要注意在选择列时,只需要选择前60列进行移除。12种方块的使用情况相当于一个约束条件:在方块放置时,该种方块的其他情况会被删除,相当于只选择了一次该方块。

#include<bits/stdc++.h>
using namespace std;
const int R=60*63,C=60+12;
const int mp[63][5][5]={
    {
        {1,0,0},
        {1,0,0},
        {1,1,1},
    },{ 
        {1,1,1},
        {1,0,0},
        {1,0,0},
    },{
        {1,1,1},
        {0,0,1},
        {0,0,1},
    },{
        {0,0,1},
        {0,0,1},
        {1,1,1},
    },{
        {1,1,1,1,1}, 
    },{
        {1},
        {1},
        {1},
        {1},
        {1},
    },{
        {0,1,0},
        {1,1,1},
        {0,1,0},
    },{
        {1,0,1},
        {1,1,1},
    },{
        {1,1},
        {1,0},
        {1,1},
    },{
        {1,1,1},
        {1,0,1},
    },{ 
        {1,1},
        {0,1},
        {1,1},
    },{
        {1,0,0,0},
        {1,1,1,1},
    },{
        {1,1},
        {1,0},
        {1,0},
        {1,0},
    },{
        {1,1,1,1},
        {0,0,0,1},
    },{
        {0,1},
        {0,1},
        {0,1},
        {1,1},
    },{
        {0,0,0,1},
        {1,1,1,1},
    },{
        {1,1},
        {0,1},
        {0,1},
        {0,1},
    },{
        {1,1,1,1},
        {1,0,0,0},
    },{
        {1,0},
        {1,0},
        {1,0},
        {1,1},
    },{
        {1,0,0},
        {1,1,0},
        {0,1,1}, 
    },{
        {0,1,1},
        {1,1,0},
        {1,0,0},
    },{
        {1,1,0},
        {0,1,1},
        {0,0,1},
    },{
        {0,0,1},
        {0,1,1},
        {1,1,0},
    },{
        {0,1,0,0},
        {1,1,1,1},
    },{
        {1,0},
        {1,1},
        {1,0},
        {1,0},
    },{
        {1,1,1,1},
        {0,0,1,0},
    },{
        {0,1},
        {0,1},
        {1,1},
        {0,1},
    },{
        {0,0,1,0},
        {1,1,1,1},
    },{
        {0,1},
        {1,1},
        {0,1},
        {0,1},
    },{
        {1,1,1,1},
        {0,1,0,0},
    },{
        {1,0},
        {1,0},
        {1,1},
        {1,0},
    },{
        {0,0,1},
        {1,1,1},
        {1,0,0},
    },{
        {1,1,0},
        {0,1,0},
        {0,1,1},
    },{
        {1,0,0},
        {1,1,1},
        {0,0,1},
    },{
        {0,1,1},
        {0,1,0},
        {1,1,0},
    },{
        {0,1,0},
        {0,1,1},
        {1,1,0},
    },{
        {1,0,0},
        {1,1,1},
        {0,1,0},
    },{
        {0,1,1},
        {1,1,0},
        {0,1,0},
    },{
        {0,1,0},
        {1,1,1},
        {0,0,1},
    },{
        {0,1,0},
        {1,1,0},
        {0,1,1},
    },{
        {0,0,1},
        {1,1,1},
        {0,1,0},
    },{
        {1,1,0},
        {0,1,1},
        {0,1,0},
    },{
        {0,1,0},
        {1,1,1},
        {1,0,0},
    },{
        {0,1,0},
        {0,1,0},
        {1,1,1},
    },{
        {1,0,0},
        {1,1,1},
        {1,0,0},
    },{
        {1,1,1},
        {0,1,0},
        {0,1,0},
    },{
        {0,0,1},
        {1,1,1},
        {0,0,1},
    },{
        {0,1,1},
        {1,1,1},
    },{
        {1,0},
        {1,1},
        {1,1},
    },{
        {1,1,1},
        {1,1,0},
    },{
        {1,1},
        {1,1},
        {0,1},
    },{
        {1,1,0},
        {1,1,1},
    },{
        {0,1},
        {1,1},
        {1,1},
    },{
        {1,1,1},
        {0,1,1},
    },{
        {1,1},
        {1,1},
        {1,0},
    },{
        {0,1,1,1},
        {1,1,0,0},
    },{
        {1,0},
        {1,1},
        {0,1},
        {0,1}, 
    },{
        {0,0,1,1},
        {1,1,1,0},
    },{
        {1,0},
        {1,0},
        {1,1},
        {0,1},
    },{
        {1,1,1,0},
        {0,0,1,1},
    },{
        {0,1},
        {1,1},
        {1,0},
        {1,0},
    },{
        {1,1,0,0},
        {0,1,1,1},
    },{
        {0,1},
        {0,1},
        {1,1},
        {1,0},
    },
};
const int fx[63]={3,3,3,3,1,5,3,2,3,2,3,2,4,2,4,2,4,2,4,3,3,3,3,2,4,2,4,2,4,2,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,2,3,2,3,2,4,2,4,2,4,2,4};
const int fy[63]={3,3,3,3,5,1,3,3,2,3,2,4,2,4,2,4,2,4,2,3,3,3,3,4,2,4,2,4,2,4,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,2,3,2,4,2,4,2,4,2,4,2};
const int ff[63]={1,1,1,1,2,2,3,4,4,4,4,5,5,5,5,5,5,5,5,6,6,6,6,7,7,7,7,7,7,7,7,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12};
int Ans,N,M,P,cnt,Cnt,sum[R+10]={},Row[R+10]={};
struct Node{
    int L,R,U,D;
    int Row,Col;
} pnt[R*C+100]={};
struct Dancing_Link_X{
    void Ready(int r,int c){
        for(int i=0;i<=c;i++){
            pnt[i].U=pnt[i].D=i;
            pnt[i].L=i-1;
            pnt[i].R=i+1;
            pnt[i].Col=i;
            sum[i]=0;
        }
        pnt[0].L=c;
        pnt[c].R=0;
        cnt=c;
        for(int i=1;i<=r;i++) Row[i]=0;
    }
    void Add(int x,int y){
        cnt++; 
        sum[y]++;
        pnt[cnt].Row=x;
        pnt[cnt].Col=y;
        pnt[cnt].U=y;
        pnt[cnt].D=pnt[y].D;
        pnt[pnt[y].D].U=cnt;
        pnt[y].D=cnt;
        if(!Row[x]){
            Row[x]=cnt;
            pnt[cnt].L=pnt[cnt].R=cnt;
        }else{
            pnt[cnt].R=Row[x];
            pnt[cnt].L=pnt[Row[x]].L;
            pnt[pnt[Row[x]].L].R=cnt;
            pnt[Row[x]].L=cnt;
            Row[x]=cnt;
        }
    }
    void del(int c){
        pnt[pnt[c].L].R=pnt[c].R;
        pnt[pnt[c].R].L=pnt[c].L;
        for(int i=pnt[c].D;i!=c;i=pnt[i].D){
            for(int j=pnt[i].R;j!=i;j=pnt[j].R){
                pnt[pnt[j].U].D=pnt[j].D;
                pnt[pnt[j].D].U=pnt[j].U;
                sum[pnt[j].Col]--;
            }
        }
    }
    void ret(int c){
        for(int i=pnt[c].U;i!=c;i=pnt[i].U){
            for(int j=pnt[i].L;j!=i;j=pnt[j].L){
                pnt[pnt[j].U].D=pnt[pnt[j].D].U=j;
                sum[pnt[j].Col]++;
            }
        }
        pnt[pnt[c].L].R=pnt[pnt[c].R].L=c;
    }
    void Dancing(int Dep){
        if(pnt[0].R>60||!pnt[0].R){
            Ans++;
            return ;
        }
        int c=pnt[0].R;
        for(int i=pnt[0].R;i&&i<=60;i=pnt[i].R){
            if(sum[i]<sum[c]) c=i;
        }
        del(c);
        for(int i=pnt[c].D;i!=c;i=pnt[i].D){
            for(int j=pnt[i].R;j!=i;j=pnt[j].R) del(pnt[j].Col);
            Dancing(Dep+1);
            for(int j=pnt[i].L;j!=i;j=pnt[j].L) ret(pnt[j].Col);
        }
        ret(c);
    }
} DLX;
int main(){
    for(;scanf("%d%d",&N,&M)!=EOF;){
        if(N>M) N^=M^=N^=M;
        if(N==1) puts("0");
        if(N==2) puts("0");
        if(N==3) puts("2");
        if(N==4) puts("368");
        if(N==5) puts("1010");
        if(N==6) puts("2339");
    }
    return 0;
    DLX.Ready(60*63,60+12);
    for(int i=0;i<63;i++){
      for(int l=1;l<=N-fx[i]+1;l++)
        for(int r=1;r<=M-fy[i]+1;r++){
            Cnt++; DLX.Add(Cnt,60+ff[i]);
            for(int L=0;L<fx[i];L++)
              for(int R=0;R<fy[i];R++){
                   if(!mp[i][L][R]) continue;
                   DLX.Add(Cnt,(l+L-1)*M+r+R);
              }
         }
    }
    DLX.Dancing(0);
    printf("%d\n",Ans/4);
    return 0;
} 

【例题4】 NQUEEN - Yet Another N-Queen Problem
在解决N皇后的问题后,LoadingTime想要解决一个更难版本的N皇后问题。在这个问题中,一些皇后已经被放在了特定的位置。希望你将其他皇后放在棋盘上,使得每两个皇后不互相攻击(同样地,皇后能攻击该行、该列即两条对角线上的所有位置)。你能帮助他解决这个问题吗?要求输出第i行上皇后的列号(保证有解)。N<=50,时限:0.640s

将皇后的N行的覆盖情况,N列的覆盖情况,2N-1 条主对角线和 2N-1 条副对角线的覆盖情况都作为Dancing Links列(共计6N-2列),将皇后在棋盘的情况看作行(最多N*N种情况)。
细节的处理上,需要注意变量的清空(多组数据);需要注意对角线不一定要都覆盖,在选择列时,只需要选择前2N列,4N-2条对角线相当于一个约束条件。在棋子放置时,与其在同一条对角线的情况会被删除。

#include<bits/stdc++.h>
using namespace std;
const int R=50*50,C=50*6-2;
int N,P,cnt,Cnt,ans,sum[R+10]={},Ans[R+10]={},Row[R+10]={},Vis[C+10]={};
inline int read(){
    char c; int Num(0),f(1);
    c=getchar();
	for(;c<'0'||c>'9';c=getchar()) if (c=='-') f=-1;
    for(;c>='0'&&c<='9';c=getchar()) Num=Num*10+(int)c-48;
    return Num*f;
}
struct Node{
	int L,R,U,D;
	int Row,Col;
} pnt[R*C+10]={};
struct kk{
	int x,y;
} vis[R+10]={},Anss[R*C+10]={};
bool mycmp(kk x,kk y){ return x.x<y.x; }
struct Dancing_Link_X{
    inline void Ready(int r,int c){
	    for(int i=0;i<=c;++i){
		    pnt[i].U=pnt[i].D=i;
		    pnt[i].L=i-1;
		    pnt[i].R=i+1;
		    pnt[i].Col=i;
		    sum[i]=0;
	    }
	    pnt[0].L=c;
	    pnt[c].R=0;
	    cnt=c;
	    for(int i=1;i<=r;++i) Row[i]=0;
    }
    inline void Add(int x,int y){
	    ++cnt; 
	    sum[y]++;
	    pnt[cnt].Row=x;
	    pnt[cnt].Col=y;
	    pnt[cnt].U=y;
	    pnt[cnt].D=pnt[y].D;
	    pnt[pnt[y].D].U=cnt;
	    pnt[y].D=cnt;
	    if(!Row[x]){
		    Row[x]=cnt;
		    pnt[cnt].L=pnt[cnt].R=cnt;
	    }else{
		    pnt[cnt].R=Row[x];
		    pnt[cnt].L=pnt[Row[x]].L;
		    pnt[pnt[Row[x]].L].R=cnt;
		    pnt[Row[x]].L=cnt;
		    Row[x]=cnt;
    	}
    }
    inline void del(int c){
	    pnt[pnt[c].L].R=pnt[c].R;
	    pnt[pnt[c].R].L=pnt[c].L;
	    for(int i=pnt[c].D;i!=c;i=pnt[i].D){
		    for(int j=pnt[i].R;j!=i;j=pnt[j].R){
			    pnt[pnt[j].U].D=pnt[j].D;
			    pnt[pnt[j].D].U=pnt[j].U;
			    --sum[pnt[j].Col];
		    }
	    }
    }
    inline void ret(int c){
	    for(int i=pnt[c].U;i!=c;i=pnt[i].U){
		    for(int j=pnt[i].L;j!=i;j=pnt[j].L){
			    pnt[pnt[j].U].D=pnt[pnt[j].D].U=j;
			    ++sum[pnt[j].Col];
		    }
	    }
	    pnt[pnt[c].L].R=pnt[pnt[c].R].L=c;
    }
    inline bool Dancing(int Dep){
	    if(!pnt[0].R||pnt[0].R>2*N){
	    	ans=Dep;
		    return true;
		}
    	if(Dep>=2*N) return false;
	    int c=pnt[0].R;
	    for(int i=pnt[0].R;i&&i<=2*N;i=pnt[i].R)
		 if(sum[i]<sum[c]) c=i;
	    del(c);
	    for(int i=pnt[c].D;i!=c;i=pnt[i].D){
	        Ans[Dep]=pnt[i].Row;
		    for(int j=pnt[i].R;j!=i;j=pnt[j].R) del(pnt[j].Col);
		    if(Dancing(Dep+1)) return true;
		    for(int j=pnt[i].L;j!=i;j=pnt[j].L) ret(pnt[j].Col);
	    }
	    ret(c);
	    return false;
    }
} DLX;
int main(){
	int A[50+10]={};
	for(;scanf("%d",&N)!=EOF&&N;){
		for(int i=1;i<=6*N-2;i++) Vis[i]=0;
        DLX.Ready(N*N,6*N-2);
		for(int i=1;i<=N;++i) A[i]=read();
		Cnt=0;
		for(int i=1;i<=N;++i){
			if(!A[i]){
				for(int j=1;j<=N;++j){
					++Cnt;
					vis[Cnt].x=i;
					vis[Cnt].y=j;
					DLX.Add(Cnt,i);
					DLX.Add(Cnt,N+j);
					DLX.Add(Cnt,2*N+N+i-j);
					DLX.Add(Cnt,4*N-1+i+j-1);
				}
			}else{
				++Cnt;
				vis[Cnt].x=i;
				vis[Cnt].y=A[i];
				DLX.Add(Cnt,i);
				DLX.Add(Cnt,N+A[i]);
				DLX.Add(Cnt,2*N+N+i-A[i]);
				DLX.Add(Cnt,4*N-1+i+A[i]-1);
			}
		}
		DLX.Dancing(0);
		for(int i=0;i<ans;++i) 
		Anss[i].x=vis[Ans[i]].x,Anss[i].y=vis[Ans[i]].y;
		sort(Anss,Anss+ans,mycmp);
		for(int i=0;i<N;++i)
		 printf("%d ",Anss[i].y);
		puts("");
	}
	return 0;
} 

二.数独系列
即可以看做特殊系列的精确覆盖问题。
以一个9*9的数独举例:

  1. 第1~81列代表81个格子的覆盖情况。
  2. 第82~2*81列代表了9行 1 ~ 9数字分防置情况
  3. 第281+1~381列代表了9列 1 ~9数字的防置情况
  4. 381+1~481列代表9个"宫" 1 ~ 9数字的防置情况

81*9行,代表81个位置防置1~9的情况。

其中,宫号的计算方式为(从0开始):宫号=(行/3)*3 + (列/3)。

【例题5】Sudoku
9*9数独。

模板,按照如上即可建图跑精确覆盖即可。

#include<bits/stdc++.h>
using namespace std;
const int R=9*9*9,C=9*9*4;
string s;
int cnt,N,a[R+10][C+10]={},A[10][10]={},Anss[10][10],Row[R+10]={},Sum[C+10]={},Ans[R]={};
struct Dancing_Link_X{
	int Up,Down,Left,Right;
	int Col,Row;
} Node[R*C+C+10]={};
void Ready(){
	for(int i=0;i<=C;i++){
		Sum[i]=0;
		Node[i].Up=i;
		Node[i].Down=i;
		Node[i].Left=i-1;
		Node[i].Right=i+1;
		Node[i].Col=i;
		Node[i].Row=0;
	}
	Node[0].Left=C;
	Node[C].Right=0;
	cnt=C;
	for(int i=1;i<=R;i++)
	 Row[i]=0;
}
void Push(int x,int y){
	cnt++;
	Sum[y]++;
	Node[cnt].Down=Node[y].Down;
	Node[cnt].Up=y;
	Node[Node[y].Down].Up=cnt;
	Node[y].Down=cnt;
	Node[cnt].Row=x;
	Node[cnt].Col=y; 
	if(!Row[x]){
		Row[x]=cnt;
		Node[cnt].Left=cnt;
		Node[cnt].Right=cnt;
	}else{
		Node[cnt].Left=Row[x];
		Node[cnt].Right=Node[Row[x]].Right;
		Node[Node[Row[x]].Right].Left=cnt;
		Node[Row[x]].Right=cnt;
	}
}
void Delete(int c){
	Node[Node[c].Left].Right=Node[c].Right;
	Node[Node[c].Right].Left=Node[c].Left;
	for(int i=Node[c].Down;i!=c;i=Node[i].Down)
	 for(int j=Node[i].Right;j!=i;j=Node[j].Right){
	 	Node[Node[j].Up].Down=Node[j].Down;
	 	Node[Node[j].Down].Up=Node[j].Up;
	 	Sum[Node[j].Col]--;
	 }
} 
void Return(int c){
	for(int i=Node[c].Up;i!=c;i=Node[i].Up)
	 for(int j=Node[i].Left;j!=i;j=Node[j].Left){
	 	Node[Node[j].Up].Down=j;
	 	Node[Node[j].Down].Up=j;
	 	Sum[Node[j].Col]++;
	 }
	Node[Node[c].Left].Right=c;
	Node[Node[c].Right].Left=c;
} 
bool Dancing(int Dep){
	if(!Node[0].Right){
		N=Dep;
	    return true;
    }
    int c=Node[0].Right;
    for(int i=Node[c].Right;i;i=Node[i].Right){
    	if(Sum[i]<Sum[c])
    	 c=i;
	}
	Delete(c);
	for(int i=Node[c].Down;i!=c;i=Node[i].Down){
		Ans[Dep]=Node[i].Row;
		for(int j=Node[i].Right;j!=i;j=Node[j].Right) Delete(Node[j].Col);
		if(Dancing(Dep+1)) return true;
		for(int j=Node[i].Left;j!=i;j=Node[j].Left) Return(Node[j].Col);
	} 
	Return(c);
	return false;
}
int Nums(int x,int y,int z){
	return x*9*9+y*9+z+1;
}
int main(){
    cin>>s;
	for(;s!="end";){
	    Ready();
	    for(int i=0;i<9;i++)
	     for(int j=0;j<9;j++)
	      if(s[i*9+j]=='.') A[i][j]=0;
	      else A[i][j]=s[i*9+j]-'0';
	    for(int i=0;i<9;i++)
	     for(int j=0;j<9;j++)
	      for(int k=0;k<9;k++) 
	       if(!A[i][j]||A[i][j]==k+1){
	       	    Push(Nums(i,j,k),Nums(0,i,j)); 
	       	    Push(Nums(i,j,k),Nums(1,i,k));
	       	    Push(Nums(i,j,k),Nums(2,j,k));
	       	    Push(Nums(i,j,k),Nums(3,(i/3)*3+(j/3),k)); 
		}
	    Dancing(0);
	    for(int i=0;i<N;i++)
	     Anss[(Ans[i]-1)/9/9][(Ans[i]-1)/9%9]=(Ans[i]-1)%9+1;
	    for(int i=0;i<9;i++)
	     for(int j=0;j<9;j++)
	      printf("%d",Anss[i][j]);
	    printf("\n");
        cin>>s;
	}
	return 0;
}

【例题6】靶形数独
9*9数独形式填数。离中心的距离不同,格子有同的价值,如 图2-1 所示。而某一格子最终的价值为该格子填入的数 * 格子的价值。求最大价值。

enter image description here

对于这道题,在9*9的数独基础上,需要将所有的情况都跑一遍,将答案不断更新。

#include<bits/stdc++.h>
using namespace std;
const int R=9*9*9,C=9*9*4;
int cnt,N,a[R+10][C+10]={},A[10][10]={},Row[R+10]={},Sum[C+10]={},Ans;
struct Dancing_Link_X{
	int Up,Down,Left,Right;
	int Col,Row;
} Node[R*C+C+10]={};
void Ready(){
	for(int i=0;i<=C;i++){
		Sum[i]=0;
		Node[i].Up=i;
		Node[i].Down=i;
		Node[i].Left=i-1;
		Node[i].Right=i+1;
		Node[i].Col=i;
		Node[i].Row=0;
	}
	Node[0].Left=C;
	Node[C].Right=0;
	cnt=C;
	for(int i=1;i<=R;i++) Row[i]=0;
}
void Push(int x,int y){
	cnt++;
	Sum[y]++;
	Node[cnt].Down=Node[y].Down;
	Node[cnt].Up=y;
	Node[Node[y].Down].Up=cnt;
	Node[y].Down=cnt;
	Node[cnt].Row=x;
	Node[cnt].Col=y; 
	if(!Row[x]){
		Row[x]=cnt;
		Node[cnt].Left=cnt;
		Node[cnt].Right=cnt;
	}else{
		Node[cnt].Left=Row[x];
		Node[cnt].Right=Node[Row[x]].Right;
		Node[Node[Row[x]].Right].Left=cnt;
		Node[Row[x]].Right=cnt;
	}
}
void Delete(int c){
	Node[Node[c].Left].Right=Node[c].Right;
	Node[Node[c].Right].Left=Node[c].Left;
	for(int i=Node[c].Down;i!=c;i=Node[i].Down)
	 for(int j=Node[i].Right;j!=i;j=Node[j].Right){
	 	Node[Node[j].Up].Down=Node[j].Down;
	 	Node[Node[j].Down].Up=Node[j].Up;
	 	Sum[Node[j].Col]--;
	 }
} 
void Return(int c){
	for(int i=Node[c].Up;i!=c;i=Node[i].Up)
	 for(int j=Node[i].Left;j!=i;j=Node[j].Left){
	 	Node[Node[j].Up].Down=j;
	 	Node[Node[j].Down].Up=j;
	 	Sum[Node[j].Col]++;
	 }
	Node[Node[c].Left].Right=c;
	Node[Node[c].Right].Left=c;
} 
bool Dancing(int Dep,int ans){
	if(!Node[0].Right){
		Ans=max(Ans,ans);
	    return true;
    }
    int c=Node[0].Right;
    for(int i=Node[c].Right;i;i=Node[i].Right){
    	if(Sum[i]<Sum[c])
    	 c=i;
	}
	Delete(c);
	for(int i=Node[c].Down;i!=c;i=Node[i].Down){
		for(int j=Node[i].Right;j!=i;j=Node[j].Right) Delete(Node[j].Col);
		Dancing(Dep+1,ans+(((Node[i].Row-1)%9+1)*(10-max(abs((Node[i].Row-1)/9/9+1-5),abs((Node[i].Row-1)/9%9+1-5)))));
		for(int j=Node[i].Left;j!=i;j=Node[j].Left) Return(Node[j].Col);
	} 
	Return(c);
	return false;
}
int Nums(int x,int y,int z){ return x*9*9+y*9+z+1; }
int main(){
	Ready();
	for(int i=0;i<9;i++)
	 for(int j=0;j<9;j++)
	  scanf("%d",&A[i][j]);
	for(int i=0;i<9;i++)
	 for(int j=0;j<9;j++)
	  for(int k=0;k<9;k++) 
	    if(!A[i][j]||A[i][j]==k+1){
	       	Push(Nums(i,j,k),Nums(0,i,j)); 
	       	Push(Nums(i,j,k),Nums(1,i,k));
	        Push(Nums(i,j,k),Nums(2,j,k));
	       	Push(Nums(i,j,k),Nums(3,(i/3)*3+(j/3),k)); 
	}
	Dancing(0,0);
	if(Ans) printf("%d\n",Ans);
	else printf("-1\n");
	return 0;
}

【例题7】Sudoku
16*16数独

较9*9来,只需要将范围改成16 * 16即可。

#include<bits/stdc++.h>
using namespace std;
const int R=16*16*16,C=16*16*4;
char s[17][17]={};
int cnt,N,a[R+10][C+10]={},A[17][17]={},Anss[17][17],Row[R+10]={},Sum[C+10]={},Ans[R]={};
struct Dancing_Link_X{
    int Up,Down,Left,Right;
    int Col,Row;
} Node[R*C+C+10]={};
void Ready(){
    for(int i=0;i<=C;i++){
        Sum[i]=0;
        Node[i].Up=i;
        Node[i].Down=i;
        Node[i].Left=i-1;
        Node[i].Right=i+1;
        Node[i].Col=i;
        Node[i].Row=0;
    }
    Node[0].Left=C;
    Node[C].Right=0;
    cnt=C;
    for(int i=1;i<=R;i++)
     Row[i]=0;
}
void Push(int x,int y){
    cnt++;
    Sum[y]++;
    Node[cnt].Down=Node[y].Down;
    Node[cnt].Up=y;
    Node[Node[y].Down].Up=cnt;
    Node[y].Down=cnt;
    Node[cnt].Row=x;
    Node[cnt].Col=y; 
    if(!Row[x]){
        Row[x]=cnt;
        Node[cnt].Left=cnt;
        Node[cnt].Right=cnt;
    }else{
        Node[cnt].Left=Row[x];
        Node[cnt].Right=Node[Row[x]].Right;
        Node[Node[Row[x]].Right].Left=cnt;
        Node[Row[x]].Right=cnt;
    }
}
void Delete(int c){
    Node[Node[c].Left].Right=Node[c].Right;
    Node[Node[c].Right].Left=Node[c].Left;
    for(int i=Node[c].Down;i!=c;i=Node[i].Down)
     for(int j=Node[i].Right;j!=i;j=Node[j].Right){
        Node[Node[j].Up].Down=Node[j].Down;
        Node[Node[j].Down].Up=Node[j].Up;
        Sum[Node[j].Col]--;
     }
} 
void Return(int c){
    for(int i=Node[c].Up;i!=c;i=Node[i].Up)
     for(int j=Node[i].Left;j!=i;j=Node[j].Left){
        Node[Node[j].Up].Down=j;
        Node[Node[j].Down].Up=j;
        Sum[Node[j].Col]++;
     }
    Node[Node[c].Left].Right=c;
    Node[Node[c].Right].Left=c;
} 
bool Dancing(int Dep){
    if(!Node[0].Right){
        N=Dep;
        return true;
    }
    int c=Node[0].Right;
    for(int i=Node[c].Right;i;i=Node[i].Right){
        if(Sum[i]<Sum[c])
         c=i;
    }
    Delete(c);
    for(int i=Node[c].Down;i!=c;i=Node[i].Down){
        Ans[Dep]=Node[i].Row;
        for(int j=Node[i].Right;j!=i;j=Node[j].Right) Delete(Node[j].Col);
        if(Dancing(Dep+1)) return true;
        for(int j=Node[i].Left;j!=i;j=Node[j].Left) Return(Node[j].Col);
    } 
    Return(c);
    return false;
}
int Nums(int x,int y,int z){
    return x*16*16+y*16+z+1;
}
int main(){
    for(;scanf("%s",s[0])!=EOF;){
    for(int i=1;i<16;i++) scanf("%s",s[i]); 
    Ready();
    for(int i=0;i<16;i++)
     for(int j=0;j<16;j++)
      if(s[i][j]=='-') A[i][j]=0;
      else A[i][j]=s[i][j]-'A'+1;
    for(int i=0;i<16;i++)
     for(int j=0;j<16;j++)
      for(int k=0;k<16;k++) 
        if(!A[i][j]||A[i][j]==k+1){
            Push(Nums(i,j,k),Nums(0,i,j)); 
            Push(Nums(i,j,k),Nums(1,i,k));
            Push(Nums(i,j,k),Nums(2,j,k));
            Push(Nums(i,j,k),Nums(3,(i/4)*4+(j/4),k)); 
    }
    Dancing(0);
    for(int i=0;i<N;i++)
     Anss[(Ans[i]-1)/16/16][(Ans[i]-1)/16%16]=(Ans[i]-1)%16+1;
    for(int i=0;i<16;i++){
     for(int j=0;j<16;j++)
        cout<<char(Anss[i][j]-1+'A');
     cout<<endl;
    }
    cout<<endl;
    }
    return 0;
}

三.重复覆盖
【引例】买点彩票压压惊
(*本题以及分析来自 英雄哪里出来 原文:https://blog.csdn.net/whereisherofrom/article/details/79220897
;有这样一种彩票,规则如下:总共八个数字,范围是[1,8]。八选五,如果五个都中,则为特等奖;如果中了其中四个,则为一等奖。作者觉得连年会都抽不到奖的人来说特等奖的概率太小了,所以对特等奖基本不抱希望。但是想尝试下一等奖,于是他想知道至少要买多少张彩票才能使得他中一等奖的概率为100%。

如果这个问题问的是让特等奖概率为100%,会变得简单许多。可以这么考虑,C(8,5)种情况下,每种情况都有可能中奖,所以每张彩票都必须买才能保证没有漏网之鱼。所以只要买下C(8,5)=56张彩票,就能保证一定有一张能中特等奖。

但是,如果五个里面中四个,情况就不一样了。假设我买了两张彩票,一张为{1,2,3,4,5},一张为{4,5,6,7,8},但是中一等奖的四个数字为{1,2,3,6}。虽然两张彩票覆盖了所有数字,但是第一张只中了三个数字{1,2,3};第二张之中了一个数字{6},因而都不算中奖。

为了应对任何一种中奖情况,我们需要做这样一件事情:从所有的C(8,5)种组合,也就是彩票中挑选出K种彩票,使得这K种彩票里能够找到任意的C(8,4)的组合({1,2,3,4},{1,2,3,5},…{4,5,6,7}…等等),并且使得这个K最小。为了使问题更加通俗易懂,我们减小数据量,考虑“四选三中二”的情况(四个数字选三个,中其中二个才算中奖)。

这是一个矩阵,矩阵的行代表所有的彩票组合(四选三),矩阵的列代表所有中奖组合(四中二)。对于每一行,如果这种组合包含对应的中奖组合,那么将它对应矩阵的位置图上颜色。为了看起来不混淆,第一种组合方案采用红色,第二种橙色,以此类推…
enter image description here
然后我们把这个矩阵数字化,有颜色的地方置为1,没有颜色的置为0,得到了如下矩阵:
enter image description here

这个矩阵有一个特点就是:“每行三个1”,这是肯定的,因为对于每一行来说,要从三个数字中挑出所有中了两个数字的情况,即C(3,2)=3。但是这不是我们关心的重点,我们关心的是如何选择一些行集合,使得所有列都能被选到(或者说覆盖到)。

问题即变成:是否存在这样一个行集合,使得集合中每一列至少一个"1"?

(转载至此)

重复覆盖的移除过程中,假定选择了第一行。那么会将第一行与之对应位上为1的列都删除掉,而不再删除与它冲突的列。这样的复杂度不再如精确覆盖般优秀,但是答案的正确性得到了保证。 以如上样例作为模板,过程如下:

enter image description here

选择第一行,删除:

enter image description here

由图可知,与第一行有冲突的行并没有删除。继续操作直到矩阵只剩下总表头(完全为空)即可。

本题即用重复覆盖+IDA*。

<代码不保证正确性>

#include<bits/stdc++.h>
using namespace std;
/*跑地很慢。。。嗯。。。很慢。。。*/ 
int Cm,Cn,Cw,cnt,Cnt,num,Ans[1010]={},Row[1010]={},Sum[1010]={};
vector<int> A[1010]={};
map<int,int> vis[1010]={};
struct Dancing_Link_X{
	int Up,Down,Left,Right;
	int Row,Col;
} pnt[100010]={};
int f(){
    int chk=0;
    bool vis[1010]={};
    for(int i=0;i<=Cnt;i++) vis[i]=true;
    for(int i=pnt[0].Right;i;i=pnt[i].Right){
        if(!vis[i]) continue;
        chk++;
        for(int j=pnt[i].Down;j!=i;j=pnt[j].Down){
            for(int k=pnt[j].Right;k!=j;k=pnt[k].Right)
            {
                vis[pnt[k].Col]=false;
                if(k&&pnt[k].Right==k) return 0;
            }
        }
    }
    return chk;
} 
void Ready(){
	for(int i=0;i<=Cnt;i++){
		pnt[i].Up=i;
		pnt[i].Down=i;
		pnt[i].Left=i-1;
		pnt[i].Right=i+1;
		pnt[i].Row=0;
		pnt[i].Col=i;
		Sum[i]=0;
	}
	num=Cnt;
	pnt[0].Left=Cnt;
	pnt[Cnt].Right=0;
}
void Push(int x,int y){
	num++; 
	Sum[y]++; 
	pnt[num].Row=x; 
	pnt[num].Col=y; 
	pnt[num].Up=y;  
	pnt[num].Down=pnt[y].Down;
	pnt[pnt[y].Down].Up=num;
	pnt[y].Down=num;
	if(!Row[x]){
		Row[x]=num; 
		pnt[num].Left=num; 
		pnt[num].Right=num;
	}else{
		pnt[num].Left=Row[x]; 
		pnt[num].Right=pnt[Row[x]].Right; 
		pnt[pnt[Row[x]].Right].Left=num;
		pnt[Row[x]].Right=num;
	}
}
void Del(int c){
	for(int i=pnt[c].Down;i!=c;i=pnt[i].Down){
	 	pnt[pnt[i].Left].Right=pnt[i].Right;
	 	pnt[pnt[i].Right].Left=pnt[i].Left;
	 }
}
void Ret(int c){
	for(int i=pnt[c].Up;i!=c;i=pnt[i].Up){
	 	pnt[pnt[i].Left].Right=i;
	 	pnt[pnt[i].Right].Left=i;
	 }
}
bool Dancing(int Dep,int Max){
    if(Dep>=Max){
	    if(!pnt[0].Right) return true;
	    else return false;
	}
	if(Dep+f()>Max) return false;
	int c=pnt[0].Right;
	for(int i=pnt[c].Right;i;i=pnt[i].Right)
	 if(Sum[i]<Sum[c]) c=i;
	for(int i=pnt[c].Down;i!=c;i=pnt[i].Down){
	    Del(i);
		for(int j=pnt[i].Right;j!=i;j=pnt[j].Right) Del(j);
	    if(Dancing(Dep+1,Max)) return true;
		for(int j=pnt[i].Left;j!=i;j=pnt[j].Left) Ret(j);
	    Ret(i);
	}
	return false;
}
void dfs1(int x,int Min){
	if(x>=Cn){
		cnt++;
		for(int i=0;i<Cn;i++) 
		 vis[cnt][Ans[i]]++;
	    return ;
	}
	for(int i=Min+1;i<=Cm;i++){
		Ans[x]=i;
		dfs1(x+1,i);
	}
}
void dfs2(int x,int Min){
	if(x>=Cw){
		Cnt++;
		for(int i=1;i<=cnt;i++){
			int f=1;
			for(int j=0;j<Cw;j++)
			 if(!vis[i][Ans[j]]){
			 	f=0;
			 	break;
			} 
			if(f) A[i].push_back(Cnt);
		}
		return ;
	}
	for(int i=Min+1;i<=Cm;i++){
	    Ans[x]=i;
		dfs2(x+1,i);
    }
}
int main(){
	scanf("%d%d%d",&Cm,&Cn,&Cw);
	dfs1(0,0);
	dfs2(0,0);
	Ready();
	for(int i=1;i<=cnt;i++)
	 for(int j=0;j<A[i].size();j++) 
	  Push(i,A[i][j]);
	int F=0; 
    for(int i=f();i<=10;i++){
	 if(Dancing(0,i)){
	 	 F++;
	     printf("%d\n",i);
		 break;	
	 }
	}
	if(!F) printf("10 or more\n");
	return 0;
} 

【例题8】SquareDestroyer
N*N用火柴组成的正方形,已经删除若干个木棍(给出木棍编号,原木棍编号如图)。求至少要删除多少木棍使得原矩阵不存在一个完整的正方形?(N<=5)
enter image description here

将每个正方形作为列,将木棍作为行,IDA*完成重复覆盖。

#include<bits/stdc++.h>
using namespace std;
int T,N,K,num,Row[1010]={},Sum[1010]={},Cut[1010]={},OK[1010]={};
vector<int> A[1010]={};
struct Dancing_Link_X{
    int Up,Down,Left,Right;
    int Row,Col;
} pnt[111111]={};
int f(){
    int chk=0;
    bool vis[1010]={};
    for(int i=0;i<=N*(N+1)*(2*N+1)/6;i++) vis[i]=true;
    for(int i=pnt[0].Right;i;i=pnt[i].Right){
        if(!vis[i]) continue;
        chk++;
        for(int j=pnt[i].Down;j!=i;j=pnt[j].Down){
            for(int k=pnt[j].Right;k!=j;k=pnt[k].Right)
            {
                vis[pnt[k].Col]=false;
                if(k&&pnt[k].Right==k) return 0;
            }
        }
    }
    return chk;
} 
void Ready(int R,int C){
    for(int i=0;i<=C;i++){
        pnt[i].Up=i;
        pnt[i].Down=i;
        pnt[i].Left=i-1;
        pnt[i].Right=i+1;
        pnt[i].Row=0;
        pnt[i].Col=i;
        Sum[i]=0;
    }
    num=C;
    pnt[0].Left=C;
    pnt[C].Right=0;
    for(int i=1;i<=R;i++) Row[i]=0;
}
void Push(int x,int y){
    num++; 
    Sum[y]++; 
    pnt[num].Row=x; 
    pnt[num].Col=y; 
    pnt[num].Up=y;  
    pnt[num].Down=pnt[y].Down;
    pnt[pnt[y].Down].Up=num;
    pnt[y].Down=num;
    if(!Row[x]){
        Row[x]=num; 
        pnt[num].Left=num; 
        pnt[num].Right=num;
    }else{
        pnt[num].Left=Row[x]; 
        pnt[num].Right=pnt[Row[x]].Right; 
        pnt[pnt[Row[x]].Right].Left=num;
        pnt[Row[x]].Right=num;
    }
}
void Del(int c){
    for(int i=pnt[c].Down;i!=c;i=pnt[i].Down){
        pnt[pnt[i].Left].Right=pnt[i].Right;
        pnt[pnt[i].Right].Left=pnt[i].Left;
     }
}
void Ret(int c){
    for(int i=pnt[c].Up;i!=c;i=pnt[i].Up){
        pnt[pnt[i].Left].Right=i;
        pnt[pnt[i].Right].Left=i;
     }
}
bool Dancing(int Dep,int Max){
    if(Dep>=Max){
        if(!pnt[0].Right) return true;
        return false;
    }
    if(f()+Dep>Max) return false;
    int c=pnt[0].Right;
    for(int i=pnt[c].Right;i;i=pnt[i].Right)
     if(Sum[i]<Sum[c]) c=i;
    for(int i=pnt[c].Down;i!=c;i=pnt[i].Down){
        Del(i);
        for(int j=pnt[i].Right;j!=i;j=pnt[j].Right) Del(j);
        if(Dancing(Dep+1,Max)) return true;
        for(int j=pnt[i].Left;j!=i;j=pnt[j].Left) Ret(j);
        Ret(i);
    }
    return false;
}
int main(){
    scanf("%d",&T);
    for(;T--;){
        scanf("%d%d",&N,&K);
        for(int i=1;i<=2*N*(N+1);i++) Cut[i]=0,A[i].clear();
        for(int i=1;i<=K;i++){
            int Temp; scanf("%d",&Temp);
            Cut[Temp]++;
        }
        Ready(2*N*(N+1),N*(N+1)*(2*N+1)/6);
        int cnt=0; 
        for(int i=1;i<=N;i++){
            for(int j=1;j<=N-i+1;j++){
                for(int k=1;k<=N-i+1;k++){
                    OK[++cnt]=true;
                    int Up=(j-1)*(2*N+1)+k;
                    for(int v=1;v<=i;v++){
                        A[Up+v-1].push_back(cnt);
                        if(Cut[Up+v-1]) OK[cnt]=false;
                    }
                    int Down=Up+i*(2*N+1);
                    for(int v=1;v<=i;v++){
                        A[Down+v-1].push_back(cnt);
                        if(Cut[Down+v-1]) OK[cnt]=false;
                    }
                    int Left=Up+N;
                    for(int v=1;v<=i;v++){
                        A[Left+(v-1)*(2*N+1)].push_back(cnt);
                        if(Cut[Left+(v-1)*(2*N+1)]) OK[cnt]=false;
                    }
                    int Right=Left+i;
                    for(int v=1;v<=i;v++){
                        A[Right+(v-1)*(2*N+1)].push_back(cnt);
                        if(Cut[Right+(v-1)*(2*N+1)]) OK[cnt]=false;
                    }
                } 
            } 
        }
        for(int i=1;i<=2*N*(N+1);i++)
         for(int j=0;j<A[i].size();j++){
            if(!OK[A[i][j]]) {
                pnt[pnt[A[i][j]].Left].Right=pnt[A[i][j]].Right;
                pnt[pnt[A[i][j]].Right].Left=pnt[A[i][j]].Left;
                continue;
            } 
            Push(i,A[i][j]);
         }
        int st=f();
        for(int i=st;i<=N*N;i++){
         if(Dancing(0,i)){
            printf("%d\n",i);
            break;
         }
        }
    }
    return 0;
} 

四.小结

  • 要注意变量的清空
  • 要注意数组越界会发生奇奇怪怪的错误
  • 不要觉得困难,多抄抄标程有助于理解
  • 其实转换没有想象中的那么难

五.参考博文

作者:英雄哪里出来;
来源:CSDN
原文:https://blog.csdn.net/whereisherofrom/article/details/79220897

六.题目拓展
(*转载自 英雄哪里出来 博文)
精确覆盖

EasyFinding ★★☆☆☆ 赤裸精确覆盖

TreasureMap ★★☆☆☆ 经典精确覆盖

Dominoes ★★★☆☆ 经典骨牌覆盖

APuzzlingProblem ★★☆☆☆ 经典骨牌覆盖

NQUEEN ★★★☆☆ N皇后问题

Lamp ★★★☆☆ 精确覆盖

PowerStations ★★★☆☆ 精确覆盖

数独系列

SudokuKiller ★☆☆☆☆ 基础题

Su-Su-Sudoku ★☆☆☆☆ 基础题

Sudoku ★☆☆☆☆ 基础题

Sudoku ★☆☆☆☆ 基础题

Sudoku ★★☆☆☆ 基础题

Sudoku ★★☆☆☆ 基础题

Sudoku ★★☆☆☆ 基础题

Sudoku ★★★☆☆ 有意思的题

Sudoku ★★★☆☆ 最全的数独题

SquigglySudoku ★★★☆☆ 数独+连通分量

重复覆盖

神龙的难题 ★★☆☆☆ 基础重复覆盖

whosyourdaddy ★★★☆☆ 重复覆盖A*基础

Radar ★★★☆☆ 二分+重复覆盖A*

Bomberman ★★★☆☆ 重复覆盖A*

RepairDepots ★★★★☆ 三角形外心+重复覆盖A*

Firestation ★★★☆☆ 重复覆盖A*

StreetFighter ★★★★☆ 精确覆盖+重复覆盖

SquareDestroyer ★★☆☆☆ 古董题

Airport ★★☆☆☆ 基础重复覆盖A*

ASimpleMathProblem ★★☆☆☆ 重复覆盖+打表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值