DLX 求解精确覆盖问题
DLX 实际上是用 Dancing Links 优化 X 算法。
精确覆盖问题
现在有一个 \(n \times m\) 的 \(01\) 矩阵,要从中选出一些行,使得每一列都有且仅有一个 \(1\)。
X 算法
这是一个搜索的算法。
我们以如下矩阵为例:
首先,我们选第一行,然后第 \(1\)、\(3\)、\(4\) 列被选中了,那么我们观察一下这些列,对于第二行,第三行,显然其在这些列也有 \(1\),那么是不能选的,删去之后我们不能继续选择,但是第二列还没有被覆盖,那么不合法,回溯,选择第二行,此时再选择第三行即可。
详细过程可以参考 OI-wiki 上对于 X 算法过程的解释。
Dancing Links
因为 X 算法有大量的实现删除一列,恢复一列的操作,所以我们考虑用一种数据结构来优化。
Dancing Links 本质上是一个双向的十字循环链表(据说是因为指针在链表间跳动像极了优美的舞蹈而得名),我们需要实现一些操作。
十字链表的具体结构可以参考 OI-wiki 的图解。
remove
删除一列,设为 \(c\)。
把 c 左右合并。
向下遍历,对于每一行合并上下。
recover
复原一列,和 remove 完全相反的操作。
build
建立每一列的指示指针即可。
insert
设插入的点为 \(x\)。
那么把 \(x\) 插到这一列的指示指针下面,\(\text{first}\) 的右边即可,这一行还没有元素的话,就令 \(\text{first} = x\)。
dance
是一个搜索函数。
int dance(int dep) {
if (!R[0]) {
Ans = dep;
return 1;
}
int c = R[0];
for (int i = R[0]; i != 0; i = R[i])
if (siz[i] < siz[c]) c = i; // 这一步是为了让搜索树的分叉更小。
remove(c);
for (int i = D[c]; i != c; i = D[i]) {
stk[dep] = row[i];
for (int j = R[i]; j != i; j = R[j]) remove(col[j]);
if (dance(dep + 1)) return 1;
for (int j = L[i]; j != i; j = L[j]) recover(col[j]); // 回溯
}
recover(c); // 回溯
return 0;
}
建模
以数独问题为例。
我们把在 \((x, y)\) 填了 \(z\) 看作是一行,那么这会覆盖第 \(x\) 行的 \(z\),覆盖第 \(y\) 列的 \(z\),覆盖某个宫的 \(z\),以及 \((x, y)\) 这个位置,比较容易漏掉的是最后的覆盖一个位置。
建模后直接 dance 即可,注意 build 的时候大小不能乱开,要精确的开!。
代码模板
#include <bits/stdc++.h>
#define int long long
#define F(i, a, b) for (int i = (a); i <= (b); i++)
#define dF(i, a, b) for (int i = (a); i >= (b); i--)
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 200005, M = (N << 1), inf = 1e16, mod = 1e9 + 7;
struct DLX {
int n, m, L[N], R[N], U[N], D[N], tot, col[N], row[N], fi[N], siz[N], Ans, stk[N];
void build(int r, int c) {
n = r, m = 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, tot = c;
memset(fi, 0, sizeof fi);
memset(siz, 0, sizeof siz);
}
void insert(int x, int y) {
col[++tot] = y, row[tot] = x, siz[y]++;
D[tot] = D[y]; U[D[y]] = tot; U[tot] = y; D[y] = tot;
if (!fi[x]) {
L[tot] = R[tot] = fi[x] = tot;
} else {
L[R[fi[x]]] = tot; R[tot] = R[fi[x]]; R[fi[x]] = tot; L[tot] = fi[x];
}
}
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], siz[col[j]]--;
}
void recover(int 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, siz[col[j]]++;
L[R[c]] = R[L[c]] = c;
}
int dance(int dep) {
if (!R[0]) {
Ans = dep;
return 1;
}
int c = R[0];
for (int i = R[0]; i != 0; i = R[i])
if (siz[i] < siz[c]) c = i;
remove(c);
for (int i = D[c]; i != c; i = D[i]) {
stk[dep] = row[i];
for (int j = R[i]; j != i; j = R[j]) remove(col[j]);
if (dance(dep + 1)) return 1;
for (int j = L[i]; j != i; j = L[j]) recover(col[j]);
}
recover(c);
return 0;
}
} solver;
signed main() {
ios_base::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
solver.build(n, m);
F(i, 1, n) {
F(j, 1, m) {
int x; cin >> x;
if (x) solver.insert(i, j);
}
}
solver.dance(1);
if (solver.Ans) {
F(i, 1, solver.Ans - 1) {
cout << solver.stk[i] << ' ';
}
} else {
cout << "No Solution!\n";
}
return 0;
}

浙公网安备 33010602011771号