「学习笔记」舞蹈链(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;
}
朝气蓬勃 后生可畏