以下转自:https://blog.csdn.net/the_star_is_at/article/details/53425736
问题描述:

给定一个n*m的矩阵,有些位置为1,有些位置为0。如果G[i][j]==1则说明i行可以覆盖j列。

Problem:

1)选定最少的行,使得每列有且仅有一个1.

2)选定最少的行,使得每列至少一个1.

DLX原理:

这类属于NP问题的问题,可以使用搜索解决。但是普通的搜索必超时无疑。因此我们要设法加优化来加快速度。

Dancing Links从数据结构方面对此类搜索进行了优化,通过仅保留矩阵中有用的部分提高了搜索速度。DLX的存储结构采用循环十字链表,在搜索过程中不断将不需要的部分切除,随着迭代深度的增加,矩阵迅速变得稀疏。

甚至一些你想不到的优化,DLX都替你想好了。

对于Problem1)的解:

转化模型:DLX精确覆盖。

DLX 精确覆盖对于当前矩阵的处理是,首先将当前要覆盖的列以及使得能够覆盖到该列的行全部去掉,然后再逐行枚举添加的方法。这是由其“有且仅有一个1”的条件 决定的。枚举某一行r,则设定当前列的解为该行r,那么该行能够覆盖到的列必然全部都可以不必再搜,因此将该行r覆盖到的列全部去掉。又由于去掉的那些列 都相当于已经有了解,那么能够覆盖到那些去掉的列的行也应当全部去掉。

搜完之后记得resume。

对于Problem2)的解:

转换模型:DLX重复覆盖。

DLX 重复覆盖对于当前矩阵的处理是,将当前列去掉,并将选作当前列的解的行能够覆盖到的列全部去掉。因为不需要每列仅由一个1去覆盖,因此不必要把能够覆盖某 一列的所有行全部去掉。因此remove和resume函数的写法将会有所不同(兽家在这里纠结了一会儿= =)。这是与Problem1)的第一个区别。

第二个区别是,由于矩阵密度下降会变慢(因为去掉的少了),因此要加上一个强剪枝。这个 剪枝利用的思想是A*搜索中的估价函数。即,对于当前的递归深度K下的矩阵,估计其最好情况下(即最少还需要多少步)才能出解。也就是,如果将能够覆盖当 前列的所有行全部选中,去掉这些行能够覆盖到的列,将这个操作作为一层深度。重复此操作直到所有列全部出解的深度是多少。如果当前深度加上这个估价函数返 回值,其和已然不能更优(也就是已经超过当前最优解),则直接返回,不必再搜。(其实平时的搜索也会不自觉地使用这个剪枝思想吧)。


以下转自:https://www.cnblogs.com/grenet/p/3145800.html

跳跃的舞者,舞蹈链(Dancing Links)算法——求解精确覆盖问题

精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1

例如:如下的矩阵

就包含了这样一个集合(第1、4、5行)

如何利用给定的矩阵求出相应的行的集合呢?我们采用回溯法

矩阵1:

先假定选择第1行,如下所示:

如上图中所示,红色的那行是选中的一行,这一行中有3个1,分别是第3、5、6列。

由于这3列已经包含了1,故,把这三列往下标示,图中的蓝色部分。蓝色部分包含3个1,分别在2行中,把这2行用紫色标示出来

根据定义,同一列的1只能有1个,故紫色的两行,和红色的一行的1相冲突。

那么在接下来的求解中,红色的部分、蓝色的部分、紫色的部分都不能用了,把这些部分都删除,得到一个新的矩阵

矩阵2:

行分别对应矩阵1中的第2、4、5行

列分别对应矩阵1中的第1、2、4、7列

于是问题就转换为一个规模小点的精确覆盖问题

在新的矩阵中再选择第1行,如下图所示

还是按照之前的步骤,进行标示。红色、蓝色和紫色的部分又全都删除,导致新的空矩阵产生,而红色的一行中有0(有0就说明这一列没有1覆盖)。说明,第1行选择是错误的

那么回到之前,选择第2行,如下图所示

按照之前的步骤,进行标示。把红色、蓝色、紫色部分删除后,得到新的矩阵

矩阵3:

行对应矩阵2中的第3行,矩阵1中的第5行

列对应矩阵2中的第2、4列,矩阵1中的第2、7列

由于剩下的矩阵只有1行,且都是1,选择这一行,问题就解决

于是该问题的解就是矩阵1中第1行、矩阵2中的第2行、矩阵3中的第1行。也就是矩阵1中的第1、4、5行

在求解这个问题的过程中,我们第1步选择第1行是正确的,但是不是每个题目第1步选择都是正确的,如果选择第1行无法求解出结果出来,那么就要推倒之前的选择,从选择第2行开始,以此类推

从上面的求解过程来看,实际上求解过程可以如下表示

1、从矩阵中选择一行

2、根据定义,标示矩阵中其他行的元素

3、删除相关行和列的元素,得到新矩阵

4、如果新矩阵是空矩阵,并且之前的一行都是1,那么求解结束,跳转到6;新矩阵不是空矩阵,继续求解,跳转到1;新矩阵是空矩阵,之前的一行中有0,跳转到5

5、说明之前的选择有误,回溯到之前的一个矩阵,跳转到1;如果没有矩阵可以回溯,说明该问题无解,跳转到7

6、求解结束,把结果输出

7、求解结束,输出无解消息

从如上的求解流程来看,在求解的过程中有大量的缓存矩阵和回溯矩阵的过程。而如何缓存矩阵以及相关的数据(保证后面的回溯能正确恢复数据),也是一个比较头疼的问题(并不是无法解决)。以及在输出结果的时候,如何输出正确的结果(把每一步的选择转换为初始矩阵相应的行)。

于是算法大师Donald E.Knuth(《计算机程序设计艺术》的作者)出面解决了这个方面的难题。他提出了DLX(Dancing Links X)算法。实际上,他把上面求解的过程称为X算法,而他提出的舞蹈链(Dancing Links)实际上并不是一种算法,而是一种数据结构。一种非常巧妙的数据结构,他的数据结构在缓存和回溯的过程中效率惊人,不需要额外的空间,以及近乎线性的时间。而在整个求解过程中,指针在数据之间跳跃着,就像精巧设计的舞蹈一样,故Donald E.Knuth把它称为Dancing Links(中文译名舞蹈链)。

Dancing Links的核心是基于双向链的方便操作(移除、恢复加入)

我们用例子来说明

假设双向链的三个连续的元素,A1、A2、A3,每个元素有两个分量Left和Right,分别指向左边和右边的元素。由定义可知

A1.Right=A2,A2.Right=A3

A2.Left=A1,A3.Left=A2

在这个双向链中,可以由任一个元素得到其他两个元素,A1.Right.Right=A3,A3.Left.Left=A1等等

现在把A2这个元素从双向链中移除(不是删除)出去,那么执行下面的操作就可以了

A1.Right=A3,A3.Left=A1

那么就直接连接起A1和A3。A2从双向链中移除出去了。但仅仅是从双向链中移除了,A2这个实体还在,并没有删除。只是在双向链中遍历的话,遍历不到A2了。

那么A2这个实体中的两个分量Left和Right指向谁?由于实体还在,而且没有修改A2分量的操作,那么A2的两个分量指向没有发生变化,也就是在移除前的指向。即A2.Left=A1和A2.Right=A3

如果此时发现,需要把A2这个元素重新加入到双向链中的原来的位置,也就是A1和A3的中间。由于A2的两个分量没有发生变化,仍然指向A1和A3。那么只要修改A1的Right分量和A3的Left就行了。也就是下面的操作

A1.Right=A2,A3.Left=A2

仔细想想,上面两个操作(移除和恢复加入)对应了什么?是不是对应了之前的算法过程中的关键的两步?

移除操作对应着缓存数据、恢复加入操作对应着回溯数据。而美妙的是,这两个操作不再占用新的空间,时间上也是极快速的

在很多实际运用中,把双向链的首尾相连,构成循环双向链

Dancing Links用的数据结构是交叉十字循环双向链

而Dancing Links中的每个元素不仅是横向循环双向链中的一份子,又是纵向循环双向链的一份子。

因为精确覆盖问题的矩阵往往是稀疏矩阵(矩阵中,0的个数多于1),Dancing Links仅仅记录矩阵中值是1的元素。

Dancing Links中的每个元素有6个分量

分别:Left指向左边的元素、Right指向右边的元素、Up指向上边的元素、Down指向下边的元素、Col指向列标元素、Row指示当前元素所在的行

Dancing Links还要准备一些辅助元素(为什么需要这些辅助元素?没有太多的道理,大师认为这能解决问题,实际上是解决了问题)

Ans():Ans数组,在求解的过程中保留当前的答案,以供最后输出答案用。

Head元素:求解的辅助元素,在求解的过程中,当判断出Head.Right=Head(也可以是Head.Left=Head)时,求解结束,输出答案。Head元素只有两个分量有用。其余的分量对求解没啥用

C元素:辅助元素,称列标元素,每列有一个列标元素。本文开始的题目的列标元素分别是C1、C2、C3、C4、C5、C6、C7。每一列的元素的Col分量都指向所在列的列标元素。列标元素的Col分量指向自己(也可以是没有)。在初始化的状态下,Head.Right=C1、C1.Right=C2、……、C7.Right=Head、Head.Left=C7等等。列标元素的分量Row=0,表示是处在第0行。

下图就是根据题目构建好的交叉十字循环双向链(构建的过程后面的详述)

就上图解释一下

每个绿色方块是一个元素,其中Head和C1、C2、……、C7是辅助元素。橙色框中的元素是原矩阵中1的元素,给他们标上号(从1到16)

左侧的红色,标示的是行号,辅助元素所在的行是0行,其余元素所在的行从1到6

每两个元素之间有一个双向箭头连线,表示双向链中相邻两个元素的关系(水平的是左右关系、垂直的是上下关系)

单向的箭头并不是表示单向关系,而因为是循环双向链,左侧的单向箭头和右侧的单向箭头(上边的和下边的)组成了一个双向箭头,例如元素14左侧的单向箭头和元素16右侧的单项箭头组成一个双向箭头,表示14.Left=16、16.Right=14;同理,元素14下边的单项箭头和元素C4上边的单向箭头组成一个双向箭头,表示14.Down=C4、C4.Up=14

接下来,利用图来解释Dancing Links是如何求解精确覆盖问题

1、首先判断Head.Right=Head?若是,求解结束,输出解;若不是,求解还没结束,到步骤2(也可以判断Head.Left=Head?)

2、获取Head.Right元素,即元素C1,并标示元素C1(标示元素C1,指的是标示C1、和C1所在列的所有元素、以及该元素所在行的元素,并从双向链中移除这些元素)。如下图中的紫色部分。

如上图可知,行2和行4中的一个必是答案的一部分(其他行中没有元素能覆盖列C1),先假设选择的是行2

3、选择行2(在答案栈中压入2),标示该行中的其他元素(元素5和元素6)所在的列首元素,即标示元素C4和标示元素C7,下图中的橙色部分。

注意的是,即使元素5在步骤2中就从双向链中移除,但是元素5的Col分量还是指向元素C4的,这里体现了双向链的强大作用。

把上图中的紫色部分和橙色部分移除的话,剩下的绿色部分就如下图所示

一下子空了好多,是不是转换为一个少了很多元素的精确覆盖问题?,利用递归的思想,很快就能写出求解的过程来。我们继续完成求解过程

4、获取Head.Right元素,即元素C2,并标示元素C2。如下图中的紫色部分。

image

如图,列C2只有元素7覆盖,故答案只能选择行3

5、选择行3(在答案栈中压入3),标示该行中的其他元素(元素8和元素9)所在的列首元素,即标示元素C3和标示元素C6,下图中的橙色部分。

把上图中的紫色部分和橙色部分移除的话,剩下的绿色部分就如下图所示

6、获取Head.Right元素,即元素C5,元素C5中的垂直双向链中没有其他元素,也就是没有元素覆盖列C5。说明当前求解失败。要回溯到之前的分叉选择步骤(步骤2)。那要回标列首元素(把列首元素、所在列的元素,以及对应行其余的元素。并恢复这些元素到双向链中),回标列首元素的顺序是标示元素的顺序的反过来。从前文可知,顺序是回标列首C6、回标列首C3、回标列首C2、回标列首C7、回标列首C4。表面上看起来比较复杂,实际上利用递归,是一件很简单的事。并把答案栈恢复到步骤2(清空的状态)的时候。又回到下图所示

7、由于之前选择行2导致无解,因此这次选择行4(再无解就整个问题就无解了)。选择行4(在答案栈中压入4),标示该行中的其他元素(元素11)所在的列首元素,即标示元素C4,下图中的橙色部分。

把上图中的紫色部分和橙色部分移除的话,剩下的绿色部分就如下图所示

8、获取Head.Right元素,即元素C2,并标示元素C2。如下图中的紫色部分。

如图,行3和行5都可以选择

9、选择行3(在答案栈中压入3),标示该行中的其他元素(元素8和元素9)所在的列首元素,即标示元素C3和标示元素C6,下图中的橙色部分。

把上图中的紫色部分和橙色部分移除的话,剩下的绿色部分就如下图所示

10、获取Head.Right元素,即元素C5,元素C5中的垂直双向链中没有其他元素,也就是没有元素覆盖列C5。说明当前求解失败。要回溯到之前的分叉选择步骤(步骤8)。从前文可知,回标列首C6、回标列首C3。并把答案栈恢复到步骤8(答案栈中只有4)的时候。又回到下图所示

11、由于之前选择行3导致无解,因此这次选择行5(在答案栈中压入5),标示该行中的其他元素(元素13)所在的列首元素,即标示元素C7,下图中的橙色部分。

把上图中的紫色部分和橙色部分移除的话,剩下的绿色部分就如下图所示

12、获取Head.Right元素,即元素C3,并标示元素C3。如下图中的紫色部分。

13、如上图,列C3只有元素1覆盖,故答案只能选择行3(在答案栈压入1)。标示该行中的其他元素(元素2和元素3)所在的列首元素,即标示元素C5和标示元素C6,下图中的橙色部分。

把上图中的紫色部分和橙色部分移除的话,剩下的绿色部分就如下图所示

14、因为Head.Right=Head。故,整个过程求解结束。输出答案,答案栈中的答案分别是4、5、1。表示该问题的解是第4、5、1行覆盖所有的列。如下图所示(蓝色的部分)

从以上的14步来看,可以把Dancing Links的求解过程表述如下

1、Dancing函数的入口

2、判断Head.Right=Head?,若是,输出答案,返回True,退出函数。

3、获得Head.Right的元素C

4、标示元素C

5、获得元素C所在列的一个元素

6、标示该元素同行的其余元素所在的列首元素

7、获得一个简化的问题,递归调用Daning函数,若返回的True,则返回True,退出函数。

8、若返回的是False,则回标该元素同行的其余元素所在的列首元素,回标的顺序和之前标示的顺序相反

9、获得元素C所在列的下一个元素,若有,跳转到步骤6

10、若没有,回标元素C,返回False,退出函数。

之前的文章的表述,为了表述简单,采用面向对象的思路,说每个元素有6个分量,分别是Left、Right、Up、Down、Col、Row分量。

但在实际的编码中,用数组也能实现相同的作用。例如:用Left()表示所有元素的Left分量,Left(1)表示元素1的Left分量

在前文中,元素分为Head元素、列首元素(C1、C2等)、普通元素。在编码中,三种元素统一成一种元素。如上题,0表示Head元素,1表示元素C1、2表示元素C2、……、7表示元素C7,从8开始表示普通元素。这是统一后,编码的简便性。利用数组的下标来表示元素,宛若指针一般。

————————————————————————————————————

以下转自:https://www.cnblogs.com/wujiechao/p/5767124.html

Example1:HUST 1017

  裸精确覆盖问题,问题如下:

1017 - Exact cover

题目描述

  There is an N*M matrix with only 0s and 1s, (1 <= N,M <= 1000). An exact cover is a selection of rows such that every column has a 1 in exactly one of the selected rows. Try to find out the selected rows.输入There are multiply test cases. First line: two integers N, M; The following N lines: Every line first comes an integer C(1 <= C <= 100), represents the number of 1s in this row, then comes C integers: the index of the columns whose value is 1 in this row.输出First output the number of rows in the selection, then output the index of the selected rows. If there are multiply selections, you should just output any of them. If there are no selection, just output “NO”.

样例输入

6 7

3 1 4 7

2 1 4

3 4 5 7

3 3 5 6

4 2 3 6 7

2 2 7

样例输出

3 2 4 6

提示来源

dupeng

题意:n*m的单位矩阵。现在要选一些行,使得这些行的集合中每列只出现一个1.
  裸精确覆盖问题,直接用dancing links 做。

#include<bits/stdc++.h>
using namespace std;#define clr(x) memset(x, 0, sizeof(x))
#define clrlow(x) memset(x, -1, sizeof(x))
#define maxnode 1000010
#define maxn 1010struct DL{int n, m;//行数 列数//U[],D[],L[],R[] 上下左右 col[],row[] 行列编号int U[maxnode], D[maxnode], L[maxnode], R[maxnode], col[maxnode], row[maxnode];int H[maxn], S[maxn];//H[]行首指针, S[]列中节点数int ansed, ans[maxn], s;//包含的行数,ans[]答案,s节点数void init(int _n, int _m){//初始化十字链表n = _n;m = _m;for(int i=0; i<=m; i++){U[i]=D[i]=i;L[i]=i-1; R[i]=i+1;col[i]=i; row[i]=0;S[i]=0;}L[0]=m; R[m]=0;s=m;//从m号元素后就表示普通元素clrlow(H);clr(ans);ansed=0;return ;}void push(int r, int c){//在第r行,c列添加新节点 注意这里是向下插入 行是前插法s++;D[s]=D[c];U[s]=c;U[D[c]]=s;D[c]=s;row[s]=r;col[s]=c;S[c]++;if(H[r]<0){//如果插入成该行第一个元素H[r]=R[s]=L[s]=s;}else{L[s]=H[r];R[s]=R[H[r]];L[R[H[r]]]=s;R[H[r]]=s;}}void del(int c){//删除列及该列上节点所在的行R[L[c]]=R[c];L[R[c]]=L[c];for(int i=D[c]; i!=c; i=D[i]){for(int j=R[i]; j!=i; j=R[j]){D[U[j]]=D[j];U[D[j]]=U[j];--S[col[j]];}}return ;}void reback(int c){//恢复第c列及该列上节点所在的行for(int i=U[c]; i!=c; i=U[i]){for(int j=L[i]; j!=i; j=L[j]){D[U[j]]=j;U[D[j]]=j;}}R[L[c]]=c;L[R[c]]=c;return ;}bool dancing(int dep){//dep为搜索深度if(R[0]==0){ansed=dep;return 1;}int c=R[0];for(int i=R[0]; i!=0; i=R[i]){if(S[i] < S[c])c = i;}del(c);for(int i=D[c]; i!=c; i=D[i]){//逐个尝试ans[dep]=row[i];for(int j=R[i]; j!=i; j=R[j]){del(col[j]);}if(dancing(dep+1))return 1;for(int j=L[i]; j!=i; j=L[j]){reback(col[j]);}}reback(c);//这个可有可无,写只是为了完整性return 0;}
}dlx;int main(){int n, m, p, k;while(scanf("%d%d", &n, &m) == 2){dlx.init(n, m);for(int i=1; i<=n; i++){scanf("%d", &p);for(int j=1; j<=p; j++){scanf("%d", &k);dlx.push(i, k);}}if(!dlx.dancing(0))printf("NO\n");else{printf("%d", dlx.ansed);for(int i=0; i<dlx.ansed; i++){printf(" %d", dlx.ans[i]);}printf("\n");}}return 0;
}

重复覆盖问题

  顾名思义,即为在01矩阵中,选出几行,使得在这几行组成的新矩阵中,每一列都有1。

  下面是hdu上一个重复覆盖的二分问题,并且给出重复覆盖问题的代码部分:

Example2:hdu 2295

Radar

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 2280 Accepted Submission(s): 897
Problem Description
N cities of the Java Kingdom need to be covered by radars for being in a state of war. Since the kingdom has M radar stations but only K operators, we can at most operate K radars. All radars have the same circular coverage with a radius of R. Our goal is to minimize R while covering the entire city with no more than K radars.

Input
The input consists of several test cases. The first line of the input consists of an integer T, indicating the number of test cases. The first line of each test case consists of 3 integers: N, M, K, representing the number of cities, the number of radar stations and the number of operators. Each of the following N lines consists of the coordinate of a city.
Each of the last M lines consists of the coordinate of a radar station.

All coordinates are separated by one space.
Technical Specification

  1. 1 ≤ T ≤ 20
  2. 1 ≤ N, M ≤ 50
  3. 1 ≤ K ≤ M
  4. 0 ≤ X, Y ≤ 1000

Output
For each test case, output the radius on a single line, rounded to six fractional digits.

Sample Input
1 3 3 2 3 4 3 1 5 4 1 1 2 2 3 3

Sample Output
2.236068

Source
The 4th Baidu Cup final

该题用二分查找radar的半径,然后用重复覆盖的DLX进行最多选取k行该半径是否完全覆盖所有城市的判断(f()函数和dep一起判断),最后精确到小数点后六位。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#define clr(x) memset(x,0,sizeof(x))
#define clrlow(x) memset(x,-1,sizeof(x))
#define maxnode 3000
#define maxn 60
using namespace std;
struct DLX//dancing links
{int U[maxnode],D[maxnode],L[maxnode],R[maxnode],col[maxnode],row[maxnode];//元素上下左右对应列对应行的指针 int S[maxn],H[maxn],V[maxn];//S为每列元素个数,H指向每行末尾的元素,V是dep()函数的标记数组。 int n,m,size,all;//all为解的行数的最大值 void init(int _n,int _m,int _all){n=_n;m=_m;all=_all;for(int i=0;i<=m;i++){L[i]=i-1;R[i]=i+1;U[i]=i;D[i]=i;row[i]=0;col[i]=i;}clr(S);clrlow(H);L[0]=m;R[m]=0;size=m;return ;}//初始化 void push(int r,int c){D[++size]=D[c];col[size]=U[size]=c;U[D[c]]=size;D[c]=size;row[size]=r;S[c]++;if(H[r]<0) {H[r]=L[size]=R[size]=size;}else{L[size]=H[r];R[size]=R[H[r]];L[R[H[r]]]=size;    R[H[r]]=size;}    return ;}//加入元素 void del(int c){S[col[c]]--;for(int i=D[c];i!=c;i=D[i]){R[L[i]]=R[i];L[R[i]]=L[i];S[col[i]]--;}return ;}//删除一列 void reback(int c){for(int i=U[c];i!=c;i=U[i]){S[col[R[L[i]]=L[R[i]]=i]]++;}S[col[c]]++;return ;}//恢复一列 int  dep( ){clr(V);int deep=0;for(int i=R[0];i!=0;i=R[i])if(!V[i]){deep++;for(int j=D[i];j!=i;j=D[j])for(int k=R[j];k!=j;k=R[k])V[col[k]]=1;}return deep;}//之后到达的最大深度//d为当前深度 bool dancing(int d){if(d+dep()>all) return false;int c=R[0];if(c==0) {return d<=all;}for(int i=R[0];i!=0;i=R[i])if(S[i]<S[c])c=i;for(int i=D[c];i!=c;i=D[i]){del(i);for(int j=R[i];j!=i;j=R[j])del(j);if(dancing(d+1)) return true;for(int j=L[i];j!=i;j=L[j])reback(j);reback(i);}return false;}//dancing
}dlx;
struct point
{int x,y;
}station[maxn],city[maxn];
double dis(point a,point b)
{return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
int main()
{int n,m,k,kase;double lt,rt,mid;double eps=1e-8;scanf("%d",&kase);while(kase--){scanf("%d%d%d",&n,&m,&k);for(int i=1;i<=n;i++)scanf("%d%d",&city[i].x,&city[i].y);for(int i=1;i<=m;i++)scanf("%d%d",&station[i].x,&station[i].y);lt=0;rt=1e8;while(rt-lt>=eps){dlx.init(m,n,k);mid=(rt+lt)/2;for(int i=1;i<=m;i++)for(int j=1;j<=n;j++)if(dis(city[j],station[i])<mid*mid-eps)dlx.push(i,j);if(dlx.dancing(0))rt=mid-eps;elselt=mid+eps;}printf("%.6lf\n",lt);}return 0;
}

代码来自:https://www.cnblogs.com/ivan-count/p/7355431.html
FZU上一道练手题

Problem 1686 神龙的难题

Accept: 717 Submit: 2140
Time Limit: 1000 mSec Memory Limit : 32768 KB
Problem Description

这是个剑与魔法的世界.英雄和魔物同在,动荡和安定并存.但总的来说,库尔特王国是个安宁的国家,人民安居乐业,魔物也比较少.但是.总有一些魔物不时会进入城市附近,干扰人民的生活.就要有一些人出来守护居民们不被魔物侵害.魔法使艾米莉就是这样的一个人.她骑着她的坐骑,神龙米格拉一起消灭干扰人类生存的魔物,维护王国的安定.艾米莉希望能够在损伤最小的前提下完成任务.每次战斗前,她都用时间停止魔法停住时间,然后米格拉他就可以发出火球烧死敌人.米格拉想知道,他如何以最快的速度消灭敌人,减轻艾米莉的负担.
Input

数据有多组,你要处理到EOF为止.每组数据第一行有两个数,n,m,(1<=n,m<=15)表示这次任务的地区范围. 然后接下来有n行,每行m个整数,如为1表示该点有怪物,为0表示该点无怪物.然后接下一行有两个整数,n1,m1 (n1<=n,m1<=m)分别表示米格拉一次能攻击的行,列数(行列不能互换),假设米格拉一单位时间能发出一个火球,所有怪物都可一击必杀.

Output

输出一行,一个整数,表示米格拉消灭所有魔物的最短时间.

Sample Input
4 4 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1 2 2 4 4 0 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0 2 2
Sample Output
4 1
Source
FOJ月赛-2009年2月- TimeLoop

/*
eg.FZU 1686 神龙的难题题意:有个n*m的矩形,每次可以把n1*m1小矩形范围内的敌人消灭,最少的次数。
思路:DLK重复覆盖模板,枚举所有的小矩形,设为行,把所有敌人编号,记为列。
*/
#include<iostream>
#include<cstdio>
#include<memory.h>
#include<algorithm>
using namespace std;
//重复覆盖:找到一些行,使得这些行的集合中每列至少有一个1
const int MN =250;//最大行数
const int MM = 250;//最大列数
const int MNN = MM*MN; //最大点数struct DLX
{int n, m, si;//n行数m列数si目前有的节点数//十字链表组成部分int U[MNN], D[MNN], L[MNN], R[MNN], Row[MNN], Col[MNN];//第i个结点的U向上指针D下L左R右,所在位置Row行Col列int H[MN], S[MM]; //记录行的选择情况和列的覆盖情况int ansd, ans[MN];//最少的次数 答案void init(int _n, int _m)  //初始化空表{n = _n;m = _m;for (int i = 0; i <= m; i++) //初始化第一横行(表头){S[i] = 0;U[i] = D[i] = i;      //目前纵向的链是空的L[i] = i - 1;R[i] = i + 1;         //横向的连起来}R[m] = 0; L[0] = m;si = m;                 //目前用了前0~m个结点for (int i = 1; i <= n; i++)H[i] = -1;}void link(int r, int c)    //插入点(r,c){++S[Col[++si] = c];     //si++;Col[si]=c;S[c]++;Row[si] = r;//si该结点的行数为rD[si] = D[c];//向下指向c的下面的第一个结点U[D[c]] = si;//c的下面的第一个结点的上面为siU[si] = c;//si的上面为列指针D[c] = si;//列指针指向的第一个该列中的元素设为siif (H[r]<0)//如果第r行没有元素H[r] = L[si] = R[si] = si;else{R[si] = R[H[r]];//si的右边为行指针所指的右边第一个元素L[R[H[r]]] = si;//行指针所指的右边第一个元素的左侧为siL[si] = H[r];//si的左侧为行指针R[H[r]] = si;//行指针的右侧为si}}void remove(int c)        //列表中删掉c列{//L[R[c]] = L[c];//表头操作  //c列头指针的右边的元素的左侧指向c列头指针左边的元素//R[L[c]] = R[c];//c列头指针的左边的元素的右侧指向c列头指针右边的元素//for (int i = D[c]; i != c; i = D[i])//遍历该列的所有元素//    for (int j = R[i]; j != i; j = R[j])//    {//对于该列的某个元素所在的行进行遍历//        U[D[j]] = U[j];//把该元素从其所在列中除去//        D[U[j]] = D[j];//        --S[Col[j]];//该元素所在的列数目减一//    }/*重复覆盖*//*c为元素编号,而非列号*//*即删除该元素所在的列,包括它自身和列头指针*/for (int i = D[c]; i != c; i = D[i]){L[R[i]] = L[i], R[L[i]] = R[i];}}void resume(int c)//恢复c列{//for (int i = U[c]; i != c; i = U[i])//枚举该列元素//    for (int j = L[i]; j != i; j = L[j])//枚举该列元素所在的行//        ++S[Col[U[D[j]] = D[U[j]] = j]];//D[U[j]]=j;U[D[j]]=j;S[Col[j]]++;//L[R[c]] = R[L[c]] = c;//c列头指针左右相连/*重复覆盖*/for (int i = U[c]; i != c; i = U[i]){L[R[i]] = R[L[i]] = i;}}bool vis[MNN];int f_check()//精确覆盖区估算剪枝{/*强剪枝。这个 剪枝利用的思想是A*搜索中的估价函数。即,对于当前的递归深度K下的矩阵,估计其最好情况下(即最少还需要多少步)才能出解。也就是,如果将能够覆盖当 前列的所有行全部选中,去掉这些行能够覆盖到的列,将这个操作作为一层深度。重复此操作直到所有列全部出解的深度是多少。如果当前深度加上这个估价函数返 回值,其和已然不能更优(也就是已经超过当前最优解),则直接返回,不必再搜。*/int ret = 0;for (int c = R[0]; c != 0; c = R[c]) vis[c] = true;for (int c = R[0]; c != 0; c = R[c])if (vis[c]){ret++;vis[c] = false;for (int i = D[c]; i != c; i = D[i])for (int j = R[i]; j != i; j = R[j])vis[Col[j]] = false;}return ret;}void dance(int d) //选取了d行 当前深度{/*重复覆盖1、如果矩阵为空,得到结果,返回2、从矩阵中选择一列,以选取最少元素的列为优化方式3、删除该列及其覆盖的行4、对该列的每一行元素:删除一行及其覆盖的列,5、进行下一层搜索,如果成功则返回6、恢复现场,跳至47、恢复所选择行*/if (d + f_check() >=ansd)return;if (R[0] == 0)//全部覆盖了{//全覆盖了之后的操作if(ansd > d) ansd=d;return;}int c = R[0];//表头结点指向的第一个列for (int i = R[0]; i != 0; i = R[i])//枚举列头指针if (S[i]<S[c])//找到列中元素个数最少的c = i;//remove(c);//将该列删去(精确覆盖)for (int i = D[c]; i != c; i = D[i]){//枚举该列的元素ans[d] = Row[i];//记录该列元素的行remove(i);//新增(重复覆盖)for (int j = R[i]; j != i; j = R[j])remove(j);//remove(Col[j])(精确覆盖)dance(d + 1);for (int j = L[i]; j != i; j = L[j])resume(j);//resume(Col[j])(精确覆盖)resume(i);//新增(重复覆盖)}//resume(c);(精确覆盖)}
}dlx;
int mp[20][20];
int main()
{int n, m,n1,m1;while (~scanf("%d%d",&n,&m)){int cnt = 0;for (int i = 1; i <= n; i++){for (int j = 1; j <= m; j++){scanf("%d", &mp[i][j]);if (mp[i][j]) mp[i][j] = ++cnt;}}scanf("%d%d", &n1, &m1);dlx.init(n*m,cnt);cnt = 1;for (int i = 1; i <=n; i++){for (int j = 1; j <=m; j++){for (int x = 0; x <n1&&i+x<=n; x++){for (int z = 0; z < m1&&z+j<=m; z++){if (mp[i+x][j+z]) dlx.link(cnt, mp[i+x][j+z]);}}cnt++;}}dlx.ansd = 0x3f3f3f3f;dlx.dance(0);printf("%d\n", dlx.ansd);}return 0;
}

dancing links(舞蹈链)——求解精准覆盖及重复覆盖问题相关推荐

  1. DLX (Dancing Links/舞蹈链)算法——求解精确覆盖问题

    精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1 例如:如下的矩阵 就包含了这样一个集合(第1.4.5行) 如何利用给定的矩阵求出相应的行的集合 ...

  2. 跳跃的舞者,舞蹈链(Dancing Links)算法——求解精确覆盖问题

    精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1 例如:如下的矩阵 就包含了这样一个集合(第1.4.5行) 如何利用给定的矩阵求出相应的行的集合 ...

  3. 算法帖——用舞蹈链算法(Dancing Links)求解俄罗斯方块覆盖问题

    问题的提出:如下图,用13块俄罗斯方块覆盖8*8的正方形.如何用计算机求解? 解决这类问题的方法不一而足,然而核心思想都是穷举法,不同的方法仅仅是对穷举法进行了优化 用13块不同形状的俄罗斯方块(每个 ...

  4. Dancing Links算法(舞蹈链)

    原文链接:跳跃的舞者,舞蹈链(Dancing Links)算法--求解精确覆盖问题 作者:万仓一黍 出处:http://grenet.cnblogs.com/ 本文版权归作者和博客园共有,欢迎转载,但 ...

  5. 舞蹈链java实现_舞蹈链(DLX) - osc_kpp7htz3的个人空间 - OSCHINA - 中文开源技术交流社区...

    #舞蹈链(DLX) Tags:搜索 ##作业部落 ##评论地址 ##一.概述 特别特别感谢这位童鞋His blog 舞蹈链是一种优美的搜索,就像下面这样跳舞- 舞蹈链用于解决精确覆盖或者重复覆盖的问题 ...

  6. Dancing Links X专题学习

    Dancing Links X专题学习 关键词:递归 | 回溯 | 深度优先 | 非确定性的 应用:精确覆盖 | 数独 | 重复覆盖 | 等 一 . 精确覆盖 [引例]Girl Match   在一个 ...

  7. Dancing Links算法

    Dancing Links略述 Dancing Links算法主要用于解决精确覆盖问题,精确覆盖问题就的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得每个集合中每一列恰好只包含一个1. ...

  8. Dancing Links DLX

    Dancing Links DLX Dancing Links 用来解精准覆盖问题. 精准覆盖问题有两种版本. 精准覆盖 : 给一个01矩阵,如何选出若干行,使得每列都有且仅有一个1. 可以求最少行数 ...

  9. Kuangbin专题三Dancing Links

    Kuangbin专题三Dancing Links 没写完所有的,因为要去上课了赶紧先预习一下,这就先发出来吧. 跳舞链这东西以前在hihocoder上翻到过,当时看的模模糊糊的,现在好好学一学. 暂时 ...

最新文章

  1. SQLDBCompare_Rel2.0破解手记
  2. Python文件基本操作
  3. python学习笔记day08 文件功能详解
  4. sringboot security基本用法
  5. Hash索引和BTree索引
  6. Android开发之ConstraintLayout(约束布局)一个控件位于一个控件右上角类似RelativeLayout实现效果
  7. Viewpager无限循环(首页与尾页平滑过渡)
  8. 通过Roslyn构建自己的C#脚本(更新版)
  9. LeetCode 1480. 一维数组的动态和(前缀和)
  10. 通过Xsheel命令:获取nginx的安装目录
  11. 基于sigmoid的文本多标签分类模型代码实现
  12. QT5之exe发布及dll打包
  13. 自动化测试方案_2第二章、自动化测试是什么?(What)
  14. AudioClip 参数解析
  15. 网络安全渗透实战详细详解
  16. npm使用及cmd常用命令
  17. matlab中integrator,matlab:Simulink Integrator的理解
  18. 组建自己的局域网(可以将PC机实现为服务器)
  19. RNN结构有什么问题?LSTM解决了RNN什么问题?怎么解决的?
  20. php 在线选座,基于jquery实现在线选座订座之影院篇

热门文章

  1. 如何通过脚本将虾米音乐的收藏列表导出成excel
  2. Revit开发获取房间内的构件
  3. 1. 使用Popup组件自定义弹框提示页面
  4. 中式别墅庭院---中国人的“中式情怀!
  5. 彻底禁用DeliveryOptimization
  6. 2008北京奥运会我心中的10大话题
  7. 《录取通知》 观后感
  8. vcredist作用
  9. 移动电影院牵手华为,落户五大洲
  10. idea中使用maven以后出现了程序包不存在的问题