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

跳舞链(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, 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;
}


继续加油~

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值