Loading

「学习笔记」舞蹈链(DLX)

这个是个算法,但是更像是数据结构,感觉理解起来还是有点绕,代码也不短,一般是解决精准覆盖问题
舞蹈链,其实就是 十字双向循环链表+搜索回溯,你可以形象地把它理解为经纬网(你也可以把精准覆盖理解为 GPS)
精准覆盖问题:给出许多集合,让你从中挑取一些集合,这些集合中的元素没有重复,能组成一个元素连续的大集合
举个例子:

\[\begin{aligned} & S_1 = \{1, 2, 5\}\\ & S_2 = \{1, 3, 4\}\\ & S_3 = \{2, 3\}\\ & S_4 = \{3, 4\} \end{aligned} \]

我们可以挑出 \(S_1\)\(S_4\) 来组成一个大的集合 \(\{ 1, 2, 3, 4, 5 \}\)
思路写出来太麻烦了,这里就只给代码了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define IT(i, A, x) for (i = A[x]; i != x; i = A[i])

inline ll read() { // 快读
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 3e4 + 5;

struct DLX { // 舞蹈链
	int n, m, idx, ans; // n 行数 m 列数 idx 节点编号 ans 答案个数
	int fir[N], siz[N]; // fir 行首指示 siz 这一列的元素个数
	int l[N], r[N], u[N], d[N]; // l 左指针 r 右指针 u 上指针 d 下指针
	int col[N], row[N], stk[N]; // col 列 row 行 stk 记录答案
	
	void build(const int &R, const int &C) { // 新建一个舞蹈链
		for (int i = 0; i <= C; ++ i) {
			l[i] = i - 1, r[i] = i + 1; // 建立列首指示
			u[i] = d[i] = i; // 由于没有元素,自己指向自己
		}
		l[0] = C, r[C] = 0, idx = C; // 循环起来
		memset(fir, 0, sizeof fir);
		memset(siz, 0, sizeof siz);
	}
	
	void insert(const int &R, const int &C) { // 在第R行第C列插入一个节点
		row[++ idx] = R, col[idx] = C;
		++ siz[C];
		u[idx] = C, d[idx] = d[C], u[d[C]] = idx, d[C] = idx;
		if (!fir[R]) {
			fir[R] = l[idx] = r[idx] = idx;
		}
		else {
			l[idx] = fir[R], r[idx] = r[fir[R]];
			l[r[fir[R]]] = idx, r[fir[R]] = idx;
		}
	}
	
	void remove(const int &C) { // 删除第c列以及相关的行和列
		int i, j;
		l[r[C]] = l[C];
		r[l[C]] = r[C];
		IT (i, d, C) { // 行
			IT (j, r, i) { // 列
				u[d[j]] = u[j], d[u[j]] = d[j];
				-- siz[col[j]];
			}
		}
	}
	
	void recover(const int &C) { // 还原第c列以及相关的行和列
		int i, j;
		IT (i, u, C) { // 行
			IT (j, l, i) { // 列
				u[d[j]] = d[u[j]] = j;
				++ siz[col[j]];
			}
		}
		l[r[C]] = r[l[C]] = C;
	}
	
	bool dance(int dep) { // 一层一层递归
		int i, j, c = r[0];
		if (!r[0]) { // 如果列首指示已经空了,说明有答案了
			ans = dep;
			return 1;
		}
		IT (i, r, 0) {
			if (siz[i] < siz[c]) { // 找一个列个数最少的
				c = i;
			}
		}
		remove(c); // 删除这一列
		IT (i, d, c) { // 从上往下找
			stk[dep] = row[i];
			IT (j, r, i) { // 从左往右找
				remove(col[j]); // 删除涉及的列
			}
			if (dance(dep + 1))	return 1; // 有答案
			IT(j, l, i) { // 回溯
				recover(col[j]); // 恢复涉及的列
			}
		}
		recover(c); // 回复这一列
		return 0;
	}
} dlx;

int main() {
	dlx.n = read(), dlx.m = read();
	dlx.build(dlx.n, dlx.m);
	for (int i = 1; i <= dlx.n; ++ i) {
		for (int j = 1; j <= dlx.m; ++ j) {
			int x = read();
			if (x) {
				dlx.insert(i, j);
			}
		}
	}
	dlx.dance(1);
	if (dlx.ans) {
		for (int i = 1; i < dlx.ans; ++ i) {
			printf("%d ", dlx.stk[i]);
		}
	}
	else {
		puts("No Solution!");
	}
	return 0;
}
posted @ 2023-01-11 21:31  yi_fan0305  阅读(109)  评论(0编辑  收藏  举报