您的位置:首页 > 其它

DLX模板之精确覆盖和重复覆盖

2017-10-03 15:33 477 查看
跳舞链(Dancing Links)求解精确覆盖问题和重复覆盖问题;

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

入门链接模板链接

模板链接中,有一句注释是错误的,下面附上自己的DLX模板。

代码(POJ-3740 250ms):

#include <cstdio>
using namespace std;
struct DLX {
const static int maxnode = 6010;
const static int maxn = 20;
const static int maxm = 310;
int n, m, size;
int row[maxnode], col[maxnode];
// 记录某个标号的节点在矩阵中的行号和列号
int U[maxnode], D[maxnode], L[maxnode], R[maxnode];
// 记录某个标号的节点的上下左右节点的编号
int H[maxn], S[maxm];
// H记录每行节点的信息(无行头),S保存某一列1的数量
int ansd, ans[maxn];
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;
}
L[0] = m, R[m] = 0;
size = m;
// 用-1表示null
for(int i = 1; i <= n; ++i) H[i] = -1;
}
void Link(int r, int c)
{
++S[col[++size] = c];
row[size] = r;
D[size] = D[c];
U[D[c]] = size;
U[size] = c;
D[c] = size;
if(H[r] < 0) H[r] = L[size] = R[size] = size;
else
{
R[size] = R[H[r]];
L[R[H[r]]] = size;
L[size] = H[r];
R[H[r]] = size;
}
}
// 对某一列进行删除,并删除当前列中其它为1的行
void remove(int c)
{
L[R[c]] = L[c];
R[L[c]] = R[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]];
}
}
// 删除的逆操作,恢复
void resume(int c)
{
L[R[c]] = c;
R[L[c]] = c;
for(int i = U[c]; i != c; i = U[i])
for(int j = L[i]; j != i; j = L[j])
{
U[D[j]] = j;
D[U[j]] = j;
++S[col[j]];
}
// 这里反不反着做是一样的。
// for(int i = D[c]; i != c; i = D[i])
// for(int j = R[i]; j != i; j = R[j])
}
// 跳舞...(d为递归的深度)
bool dance(int d)
{
if(R[0] == 0)
{
ansd = d;
return true;
}
int c = R[0];
// 小优化,1越少的列,寻找到需要的行的概率越大
for(int i = R[0]; i; 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];
for(int j = R[i]; j != i; j = R[j])
remove(col[j]);
if(dance(d+1)) return true;
// 这里必须反着恢复
for(int j = L[i]; j != i; j = L[j])
resume(col[j]);
}
resume(c);
return false;
}
} dlx;
int main()
{
int n, m, x;
while(scanf("%d %d", &n, &m) != EOF)
{
dlx.init(n, m);
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
{
scanf("%d", &x);
if(x) dlx.Link(i, j);
}
if(!dlx.dance(0)) printf("It is impossible\n");
else printf("Yes, I found it\n");
}
return 0;
}

无注释版本:

#include <cstdio>
using namespace std;
struct DLX {
const static int maxnode = 6010;
const static int maxn = 20;
const static int maxm = 310;
int n, m, size;
int row[maxnode], col[maxnode];
int U[maxnode], D[maxnode], L[maxnode], R[maxnode];
int H[maxn], S[maxm];
int ansd
fca0
, ans[maxn];
void init(int _n, int _m)
{
n = _n, m = _m, size = _m;
for(int i = 0; i <= m; ++i)
{
S[i] = 0;
U[i] = D[i] = i;
L[i] = i-1, R[i] = i+1;
}
L[0] = m, R[m] = 0;
for(int i = 1; i <= n; ++i) H[i] = -1;
}
void Link(int r, int c)
{
++S[col[++size] = c];
row[size] = r;
D[size] = D[c];
U[D[c]] = size;
U[size] = c;
D[c] = size;
if(H[r] < 0) H[r] = L[size] = R[size] = size;
else
{
R[size] = R[H[r]];
L[R[H[r]]] = size;
L[size] = H[r];
R[H[r]] = size;
}
}
void remove(int c)
{
L[R[c]] = L[c], R[L[c]] = R[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]];
}
void resume(int c)
{
L[R[c]] = R[L[c]] = c;
for(int i = U[c]; i != c; i = U[i])
for(int j = L[i]; j != i; j = L[j])
U[D[j]] = D[U[j]] = j, ++S[col[j]];
}
bool dance(int d)
{
if(R[0] == 0)
{
ansd = d;
return true;
}
int c = R[0];
for(int i = R[0]; i; 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];
for(int j = R[i]; j != i; j = R[j])
remove(col[j]);
if(dance(d+1)) return true;
for(int j = L[i]; j != i; j = L[j])
resume(col[j]);
}
resume(c);
return false;
}
} dlx;
int main()
{
int n, m, x;
while(scanf("%d %d", &n, &m) != EOF)
{
dlx.init(n, m);
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
{
scanf("%d", &x);
if(x) dlx.Link(i, j);
}
if(!dlx.dance(0)) printf("It is impossible\n");
else printf("Yes, I found it\n");
}
return 0;
}

重复覆盖问题:选定最少的行,使得每列至少有一个1。

模板链接模板链接

其实是准确覆盖的转化模型。

首先选择当前要覆盖的列,将该列删除,枚举覆盖到该列的所有行:对于某一行r,假设认为它是解集中的一个,那么该行所能覆盖到的列都不必再搜,所以删除该行覆盖到的所有列。注意此时不用删去覆盖到这些列的其它行,因为一列中允许有多个1。

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

代码(HDU-3498 2168ms):

#include <cstdio>
#include <string.h>
using namespace std;
struct DLX {
const static int inf = 0x3f3f3f3f;
const static int maxnode = 4010;
const static int maxn = 60;
const static int maxm = 60;
int n, m, size;
int row[maxnode], col[maxnode];
int U[maxnode], D[maxnode], L[maxnode], R[maxnode];
int H[maxn], S[maxm];
int ansd;
bool vis[maxm];
void init(int _n, int _m)
{
n = _n, m = _m, size = _m, ansd = inf;
for(int i = 0; i <= m; ++i)
{
S[i] = 0;
U[i] = D[i] = i;
L[i] = i-1, R[i] = i+1;
}
L[0] = m, R[m] = 0;
for(int i = 1; i <= n; ++i) H[i] = -1;
}
void Link(int r, int c)
{
++S[col[++size] = c];
row[size] = r;
D[size] = D[c];
U[D[c]] = size;
U[size] = c;
D[c] = size;
if(H[r] < 0) H[r] = L[size] = R[size] = size;
else
{
R[size] = R[H[r]];
L[R[H[r]]] = size;
L[size] = H[r];
R[H[r]] = size;
}
}
// 删除和恢复和精确覆盖有所不同
void remove(int 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)
{
for(int i = U[c]; i != c; i = U[i])
L[R[i]] = R[L[i]] = i;
}
// 预估函数
int h()
{
int ret = 0;
memset(vis, 0, sizeof vis);
for(int i = R[0]; i; i = R[i])
{
if(vis[i]) continue;
++ret; vis[i] = true;
for(int j = D[i]; j != i; j = D[j])
for(int k = R[j]; k != j; k = R[k])
vis[col[k]] = true;
}
return ret;
}
void dance(int d)
{
if(d+h() >= ansd) return;
if(R[0] == 0)
{
if(d < ansd) ansd = d;
return;
}
int c = R[0];
for(int i = R[0]; i; i = R[i])
if(S[i] < S[c]) c = i;
for(int i = D[c]; i != c; i = D[i])
{
//下面的删除和恢复也有所不同
remove(i);
for(int j = R[i]; j != i; j = R[j])
remove(j);
dance(d+1);
for(int j = L[i]; j != i; j = L[j])
resume(j);
resume(i);
}
return;
}
} dlx;
int main()
{
int t, n, m, x, y;
while(scanf("%d %d", &n, &m) != EOF)
{
dlx.init(n, n);
for(int i = 1; i <= n; ++i)
dlx.Link(i, i);
for(int j = 1; j <= m; ++j)
{
scanf("%d %d", &x, &y);
dlx.Link(x, y);
dlx.Link(y, x);
}
dlx.dance(0);
printf("%d\n", dlx.ansd);
}
return 0;
}

继续加油~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: