Dancing Links
精确覆盖问题¶
定义¶
精确覆盖问题 (Exact Cover Problem) 是指给定许多集合
(1)
(2)
(3)
例如,若给出
则
问题转化¶
我们将
给定一个 01 矩阵,你可以选择一些行,使得最终每列都恰好有一个 1。 举个例子,我们对上文中的例子进行建模,可以得到这么一个矩阵:
其中第
暴力 1¶
我们可以枚举选择哪些行,最后检查这个方案是否合法。
因为每一行都有选或者不选两种状态,所以枚举行的时间复杂度是
而每次检查都需要
代码实现
int ok = 0;
for(int state = 0; state < 1 << n; ++state) { // 枚举每行是否被选
for(int i = 1; i <= n; ++i) if((1 << i - 1) & state)
for(int j = 1; j <= m; ++j)
a[i][j] = 1;
int flag = 1;
for(int j = 1; j <= m; ++j) for(int i = 1, bo = 0; i <= n; ++i)
if(a[i][j]) {
if(bo) flag = 0;
else bo = 1;
}
if(!flag) continue;
else {
ok = 1;
for(int i = 1; i <= n; ++i) if((1 << i - 1) & state)
printf("%d ", i);
puts("");
}
memset(a, 0, sizeof(a));
}
if(!ok) puts("No solution.");
暴力 2¶
考虑到 01 矩阵的特殊性质,我们可以把每一行都看做成一个
因此被转化为了
给你
个 n 位二进制数,要求选择一些数,使得任意两个数的与都为 0,且所有数的或为 m 。 2^m - 1 tmp
表示的是截至目前的所有被选择了的位二进制数的或。 m
因为每一行都有选或者不选两种状态,所以枚举行的时间复杂度为
而每次计算 tmp
都需要
代码实现
int ok = 0;
for(int i = 1; i <= n; ++i)
for(int j = m; j >= 1; --j)
num[i] = num[i] << 1 | a[i][j];
for(int state = 0; state < 1 << n; ++state) {
int tmp = 0;
for(int i = 1; i <= n; ++i) if((1 << i - 1) & state) {
if(tmp & num[i]) break;
tmp |= num[i];
}
if(tmp == (1 << m) - 1) {
ok = 1;
for(int i = 1; i <= n; ++i) if((1 << i - 1) & state)
printf("%d ", i);
puts("");
}
}
if(!ok) puts("No solution.");
X 算法¶
Donald E. Knuth 提出了 X 算法 (Algorithm X),其思想与刚才的暴力差不多,但是方便优化。
继续以上文中中提到的例子为载体,我们得到的是一个这样的 01 矩阵:
- 此时第一行有
3 1 3 1 3 1 2 1 2 1 3 1 1
- 选择所有被标记的列,将它们删除,并将这些列中含
1
- 选择所有被标记的行,将它们删除;
这表示表示我们选择了一行,且这一行的所有
于是我们得到了这样的一个新的小 01 矩阵:
- 此时第一行(原来的第二行)有
3 1 2 1 2 1 1
- 选择所有被标记的列,将它们删除,并将这些列中含
1
- 选择所有被标记的行,将它们删除;
于是我们得到了一个空矩阵。但是上次删除的行 "1 0 1 1" 不是全
- 回溯到步骤
4 1
- 选择所有被标记的列,将它们删除,并将这些列中含
1
- 选择所有被标记的行,将它们删除;
于是我们得到了这样的一个矩阵:
- 此时第一行(原来的第五行)有
2 1
-
上一次删除的时候,删除的是全
1 答案即为我们删除的三行:
1, 4, 5 -
强烈建议自己模拟一遍矩阵删除、还原与回溯的过程后再接着阅读下文。
我们可以概括出 X 算法的过程:
-
对于现在的矩阵
M r r S -
如果尝试了所有的
r -
标记与
r r_i c_i -
删除所有标记的行和列,得到新矩阵
M' -
如果
M' r 1 S 如果
M' r 1 r r_i c_i 1 如果
M' 1
不难看出,X 算法需要大量的“删除行”、“删除列”和“恢复行”、“恢复列”的操作。
Donald E. Knuth 想到了用双向十字链表来维护这些操作。
而在双向十字链表上不断跳跃的过程被形象地比喻成“跳跃”,因此被用来优化 X 算法的双向十字链表也被称为“Dancing Links”。
Dancing Links 优化的 X 算法¶
预编译命令¶
#define IT(i, A, x) for (i = A[x]; i != x; i = A[i])
定义¶
既然是双向十字链表,那么一定是有四个指针域的:一个指上方的元素,一个指下方的元素,一个指左边的元素,一个指右边的元素。而每个元素
是不是非常简单?
而其实大型双向链表其实是长这样的:
每一行都有一个行首指示,每一列都有一个列指示。
行首指示为 first[]
,列指示是我们虚拟出的
同时,每一列都有一个 siz[]
表示这一列的元素个数。
特殊地,
static const int MS = 1e5 + 5;
int n, m, idx, first[MS], siz[MS];
int L[MS], R[MS], U[MS], D[MS];
int col[MS], row[MS];
remove 操作¶
我们先将
(1)
(2)
即 L[R[c]] = L[c], R[L[c]] = R[c];
。
然后我们要顺着这一列往下走,把走过的每一行都删掉。
如何删掉每一行呢?枚举当前行的指针
(1)
(2)
注意要修改每一列的元素个数。
即 U[D[j]] = U[j], D[U[j]] = D[j], --siz[col[j]];
。
因此
其中第一个 IT(i, D, c)
等价于 for(i = D[c]; i != c; i = D[i])
,即在顺着这一列从上往下遍历;
第二个 IT(j, R, i)
等价于 for(j = R[i]; j != i; j = R[j])
,即在顺着这一行从左往右遍历。
void remove(const int &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]];
}
recover 操作¶
值得注意的是,
在这里给出
void recover(const int &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;
}
build 操作¶
我们新建
第
特殊地,
于是我们得到了一条链:
void build(const int &r, const 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, idx = c;
memset(first, 0, sizeof(first));
memset(siz, 0, sizeof(siz));
}
这样就初始化了一个 Dancing Links。
insert 操作¶
我们分两种情况来操作:
(1) 如果第
(2) 如果第
对于 (1),我们可以通过 first[r] = L[idx] = R[idx] = idx;
来实现;
对于 (2),(我们称这个新元素为
-
我们把
idx c (1)
idx c (2)
idx c idx (3)
idx c (4)
c idx 注意记录
idx col[++idx] = c, row[idx] = r, ++siz[c]; U[idx] = c, D[idx] = D[c], U[D[c]] = idx, D[c] = idx;
强烈建议读者完全掌握这几步的顺序后再继续阅读本文。
-
我们把
idx first(r) (1)
idx first(r) (2) 原来
first(r) idx (3)
idx first(r) (4)
first(r) idx L[idx] = first[r], R[idx] = R[first[r]]; R[first[r]] = idx, L[R[first[r]]] = idx;
强烈建议读者完全掌握这几步的顺序后再继续阅读本文。
对于
留心曲线箭头的方向。
在这里给出
void insert(const int &r, const int &c) {
row[++idx] = r, col[idx] = c, ++siz[c];
U[idx] = D[idx] = c, U[D[c]] = idx, D[c] = idx;
if (!first[r])
first[r] = L[idx] = R[idx] = idx;
else {
L[idx] = first[r], R[idx] = R[first[r]];
L[R[first[r]]] = idx, R[first[r]] = idx;
}
}
dance 操作¶
(1) 如果
(2) 选择列元素个数最少的一列,并删掉这一列;
(3) 遍历这一列所有有
(4) 递归调用
(5) 如果无解,则返回;
在这里给出
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;
}
其中 stk[]
用来记录答案。
注意我们每次优先选择列元素个数最少的一列进行删除,这样能保证程序具有一定的启发性,使搜索树分支最少。
模板¶
模板代码
#include <bits/stdc++.h>
#define ll long long
#define rgi register int
#define rgl register ll
#define il inline
const int N = 500 + 10;
int n, m, idx, ans;
int first[N], siz[N], stk[N];
struct DLXNODE {
int lc, rc, up, dn, r, c;
};
il int read() {
rgi x = 0, f = 0, ch;
while(!isdigit(ch = getchar())) f |= ch == '-';
while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
return f ? -x : x;
}
struct DLX {
static const int MAXSIZE = 1e5 + 10;
#define IT(i, A, x) for(i = A[x]; i != x; i = A[i])
int n, m, tot, first[MAXSIZE + 10], siz[MAXSIZE + 10];
int L[MAXSIZE + 10], R[MAXSIZE + 10], U[MAXSIZE + 10], D[MAXSIZE + 10];
int col[MAXSIZE + 10], row[MAXSIZE + 10];
void build(const int &r, const int &c) {
n = r, m = c;
for(rgi 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(first, 0, sizeof(first));
memset(siz, 0, sizeof(siz));
}
void insert(const int &r, const int &c) {
col[++tot] = c, row[tot] = r, ++siz[c];
D[tot] = D[c], U[D[c]] = tot, U[tot] = c, D[c] = tot;
if(!first[r]) first[r] = L[tot] = R[tot] = tot;
else {
R[tot] = R[first[r]], L[R[first[r]]] = tot;
L[tot] = first[r], R[first[r]] = tot;
}
}
void remove(const int &c) {
rgi 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) {
rgi 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) {
if(!R[0]) { ans = dep; return 1; }
rgi i, j, c = R[0];
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;
}
#undef IT
} solver;
int main() {
n = read(), m = read();
solver.build(n, m);
for(rgi i = 1; i <= n; ++i) for(rgi j = 1; j <= m; ++j) {
int x = read();
if(x) solver.insert(i, j);
} solver.dance(1);
if(ans)
for(rgi i = 1; i < ans; ++i) printf("%d ", stk[i]);
else
puts("No Solution!");
return 0;
}
时间复杂度分析¶
DLX 的时间复杂度是 指数级 的,它递归及回溯的次数与矩阵中
因此理论复杂度大概在
但实际情况下 DLX 表现良好,一般能解决大部分的问题。
如何建模¶
DLX 的难点,不全在于链表的建立,而在于建模。
请确保已经完全掌握 DLX 模板后再继续阅读本文。
我们每拿到一个题,应该考虑行和列所表示的意义:
-
行表示决策,因为每行对应着一个集合,也就对应着选/不选;
-
列表示状态,因为第
i P_i
对于某一行而言,由于不同的列的值不尽相同,我们 由不同的状态,定义了一个决策 。
-
解题思路
先考虑决策是什么。
在这一题中,每一个决策可以用形如
(r, c, w) 注意到 “宫” 并不是决策的参数,因为它 可以被每个确定的
(r, c) 因此有
9 \times 9 \times 9 = 729 再考虑状态是什么。
我们思考一下
(r, c, w) (r, c) b (1) 第
r w 9 \times 9 = 81 (2) 第
c w 9 \times 9 = 81 (3) 第
b w 9 \times 9 = 81 (4)
(r, c) 9 \times 9 = 81 因此有
81 \times 4 = 324 729 \times 4 = 2916 1 至此,我们成功地将
9 \times 9 729 324 2916 1 参考代码
#include <bits/stdc++.h> #define LL long long #define rgi register int #define il inline const int N = 1e6 + 10; #define JUDGE 0 #define DEBUG 0 int ans[10][10], stk[N]; il int read() { rgi x = 0, f = 0, ch; while(!isdigit(ch = getchar())) f |= ch == '-'; while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar(); return f ? -x : x; } struct DLX { static const int MAXSIZE = 1e5 + 10; #define IT(i, A, x) for(i = A[x]; i != x; i = A[i]) int n, m, tot, first[MAXSIZE + 10], siz[MAXSIZE + 10]; int L[MAXSIZE + 10], R[MAXSIZE + 10], U[MAXSIZE + 10], D[MAXSIZE + 10]; int col[MAXSIZE + 10], row[MAXSIZE + 10]; void build(const int &r, const int &c) { n = r, m = c; for(rgi 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(first, 0, sizeof(first)); memset(siz, 0, sizeof(siz)); } void insert(const int &r, const int &c) { col[++tot] = c, row[tot] = r, ++siz[c]; D[tot] = D[c], U[D[c]] = tot, U[tot] = c, D[c] = tot; if(!first[r]) first[r] = L[tot] = R[tot] = tot; else { R[tot] = R[first[r]], L[R[first[r]]] = tot; L[tot] = first[r], R[first[r]] = tot; } } void remove(const int &c) { rgi 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) { rgi 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) { rgi i, j, c = R[0]; if(!R[0]) { for(i = 1; i < dep; ++i) { int x = (stk[i] - 1) / 9 / 9 + 1; int y = (stk[i] - 1) / 9 % 9 + 1; int v = (stk[i] - 1) % 9 + 1; ans[x][y] = v; } 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; } } solver; int GetId(int row, int col, int num) { return (row - 1) * 9 * 9 + (col - 1) * 9 + num; } void Insert(int row, int col, int num) { int dx = (row - 1) / 3 + 1; int dy = (col - 1) / 3 + 1; int room = (dx - 1) * 3 + dy; int id = GetId(row, col, num); int f1 = (row - 1) * 9 + num; // task 1 int f2 = 81 + (col - 1) * 9 + num; // task 2 int f3 = 81 * 2 + (room - 1) * 9 + num; // task 3 int f4 = 81 * 3 + (row - 1) * 9 + col; // task 4 solver.insert(id, f1); solver.insert(id, f2); solver.insert(id, f3); solver.insert(id, f4); } int main() { #if JUDGE freopen(".in", "r", stdin); freopen(".out", "w", stdout); #endif solver.build(729, 324); for(rgi i = 1; i <= 9; ++i) for(rgi j = 1; j <= 9; ++j) { ans[i][j] = read(); for(rgi v = 1; v <= 9; ++v) { if(ans[i][j] && ans[i][j] != v) continue; Insert(i, j, v); } } solver.dance(1); for(rgi i = 1; i <= 9; ++i, putchar('\n')) for(rgi j = 1; j <= 9; ++j, putchar(' ')) printf("%d", ans[i][j]); return 0; }
-
参考代码
#include <bits/stdc++.h> #define LL long long #define il inline const int oo = 0x3f3f3f3f; const int N = 1e5 + 10; const int e[] = { 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 6, 6, 7, 8, 8, 8, 8, 8, 7, 6, 6, 7, 8, 9, 9, 9, 8, 7, 6, 6, 7, 8, 9, 10, 9, 8, 7, 6, 6, 7, 8, 9, 9, 9, 8, 7, 6, 6, 7, 8, 8, 8, 8, 8, 7, 6, 6, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 }; int ans = -oo, a[10][10], stk[N]; il int read() { int x = 0, f = 0, ch; while(!isdigit(ch = getchar())) f |= ch == '-'; while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar(); return f ? -x : x; } int GetWeight(int row, int col, int num) { return num * e[(row - 1) * 9 + (col - 1)]; } struct DLX { static const int MAXSIZE = 1e5 + 10; #define IT(i, A, x) for(i = A[x]; i != x; i = A[i]) int n, m, tot, first[MAXSIZE + 10], siz[MAXSIZE + 10]; int L[MAXSIZE + 10], R[MAXSIZE + 10], U[MAXSIZE + 10], D[MAXSIZE + 10]; int col[MAXSIZE + 10], row[MAXSIZE + 10]; void build(const int &r, const 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(first, 0, sizeof(first)); memset(siz, 0, sizeof(siz)); } void insert(const int &r, const int &c) { col[++tot] = c, row[tot] = r, ++siz[c]; D[tot] = D[c], U[D[c]] = tot, U[tot] = c, D[c] = tot; if(!first[r]) first[r] = L[tot] = R[tot] = tot; else { R[tot] = R[first[r]], L[R[first[r]]] = tot; L[tot] = first[r], R[first[r]] = tot; } } void remove(const int &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) { 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; } void dance(int dep) { int i, j, c = R[0]; if(!R[0]) { int cur_ans = 0; for(i = 1; i < dep; ++i) { int cur_row = (stk[i] - 1) / 9 / 9 + 1; int cur_col = (stk[i] - 1) / 9 % 9 + 1; int cur_num = (stk[i] - 1) % 9 + 1; cur_ans += GetWeight(cur_row, cur_col, cur_num); } ans = std::max(ans, cur_ans); return; } 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]); dance(dep + 1); IT(j, L, i) recover(col[j]); } recover(c); } } solver; int GetId(int row, int col, int num) { return (row - 1) * 9 * 9 + (col - 1) * 9 + num; } void Insert(int row, int col, int num) { int dx = (row - 1) / 3 + 1; // r int dy = (col - 1) / 3 + 1; // c int room = (dx - 1) * 3 + dy; // room int id = GetId(row, col, num); int f1 = (row - 1) * 9 + num; // task 1 int f2 = 81 + (col - 1) * 9 + num; // task 2 int f3 = 81 * 2 + (room - 1) * 9 + num; // task 3 int f4 = 81 * 3 + (row - 1) * 9 + col; // task 4 solver.insert(id, f1); solver.insert(id, f2); solver.insert(id, f3); solver.insert(id, f4); } int main() { solver.build(729, 324); for(int i = 1; i <= 9; ++i) for(int j = 1; j <= 9; ++j) { a[i][j] = read(); for(int v = 1; v <= 9; ++v) { if(a[i][j] && v != a[i][j]) continue; Insert(i, j, v); } } solver.dance(1); printf("%d", ans == -oo ? -1 : ans); return 0; }
-
解题思路
定义:题中给我们的智慧珠的形态,称为这个智慧珠的 标准形态。
显然,我们可以通过改变两个参数
d 90^{\circ} f 仍然,我们先考虑决策是什么。
在这一题中,每一个决策可以用形如
(v, d, f, i) 表示第
i v d 90^{\circ} 巧合的是,我们可以令
f = 1 f = -1 因此有
55 \times 4 \times 2 \times 12 = 5280 需要注意的是,因为一些不合法的填充,如
(1, 0, 1, 4) 所以在实际操作中,空的智慧珠棋盘也只需要建出
2730 再考虑状态是什么。
这一题的状态比较简单。
我们思考一下,
(v, d, f, i) (1) 某些格子被占了(用
55 (2) 第
i 12 因此有
55 + 12 = 67 5280 \times (5 + 1) = 31680 1 至此,我们成功地将智慧珠游戏转化成了一个有
5280 67 31680 1 参考代码
#include <bits/stdc++.h> #define LL long long int numcol, numrow; int dfn[3000], tx[2], nxt[2], num[50][50], vis[50]; char ans[50][50]; const int f[2] = { -1, 1 }; const int table[12][5][2] = { // directions of shapes { { 0, 0 }, { 1, 0 }, { 0, 1 } }, // A { { 0, 0 }, { 0, 1 }, { 0, 2 }, { 0, 3 } }, // B { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 0, 2 } }, // C { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } }, // D { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 2, 1 }, { 2, 2 } }, // E { { 0, 0 }, { 0, 1 }, { 1, 1 }, { 0, 2 }, { 0, 3 } }, // F { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 0, 2 }, { 1, 2 } }, // G { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 }, { 0, 2 } }, // H { { 0, 0 }, { 0, 1 }, { 0, 2 }, { 1, 2 }, { 1, 3 } }, // I { { 0, 0 }, { -1, 1 }, { 0, 1 }, { 1, 1 }, { 0, 2 } }, // J { { 0, 0 }, { 1, 0 }, { 1, 1 }, { 2, 1 }, { 2, 2 } }, // K { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 0, 2 }, { 0, 3 } }, // L }; const int len[12] = { 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5 }; const int getx[] = { 0, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14 }; const int gety[] = { 0, 1, 1, 2, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; struct DLX { static const int MS = 1e5 + 10; #define IT(i, A, x) for (i = A[x]; i != x; i = A[i]) int n, m, tot, first[MS], siz[MS]; int L[MS], R[MS], U[MS], D[MS]; int col[MS], row[MS]; void build(const int &r, const int &c) { n = r, m = c; for (rgi 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(first, 0, sizeof(first)); memset(siz, 0, sizeof(siz)); } void insert(const int &r, const int &c) { col[++tot] = c, row[tot] = r, ++siz[c]; D[tot] = D[c], U[D[c]] = tot, U[tot] = c, D[c] = tot; if (!first[r]) first[r] = L[tot] = R[tot] = tot; else R[tot] = R[first[r]], L[R[first[r]]] = tot, L[tot] = first[r], R[first[r]] = tot; // ! } void remove(const int &c) { rgi 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) { rgi 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() { if (!R[0]) return 1; rgi i, j, c = R[0]; IT(i, R, 0) if (siz[i] < siz[c]) c = i; remove(c); IT(i, D, c) { if (col[i] <= 55) ans[getx[col[i]]][gety[col[i]]] = dfn[row[i]] + 'A'; IT(j, R, i) { remove(col[j]); if (col[j] <= 55) ans[getx[col[j]]][gety[col[j]]] = dfn[row[j]] + 'A'; } if (dance()) return 1; IT(j, L, i) recover(col[j]); } recover(c); return 0; } #undef IT } solver; int main() { for (rgi i = 1; i <= 10; ++i) scanf("%s", ans[i] + 1); for (rgi i = 1; i <= 10; ++i) for (rgi j = 1; j <= i; ++j) { if (ans[i][j] != '.') vis[ans[i][j] - 'A'] = 1; num[i][j] = ++numcol; } solver.build(2730, numcol + 12); /*******build*******/ for (rgi id = 0, op; id < 12; ++id) { // every block for (++numcol, op = 0; op <= 1; ++op) { for (rgi dx = 0; dx <= 1; ++dx) { for (rgi dy = 0; dy <= 1; ++dy) { for (tx[0] = 1; tx[0] <= 10; ++tx[0]) { for (tx[1] = 1; tx[1] <= tx[0]; ++tx[1]) { bool flag = 1; for (rgi k = 0; k < len[id]; ++k) { nxt[op] = tx[op] + f[dx] * table[id][k][0]; nxt[op ^ 1] = tx[op ^ 1] + f[dy] * table[id][k][1]; if (vis[id]) { if (ans[nxt[0]][nxt[1]] != id + 'A') { flag = 0; break; } } else if (ans[nxt[0]][nxt[1]] != '.') { flag = 0; break; } } if (!flag) continue; dfn[++numrow] = id; solver.insert(numrow, numcol); for (rgi k = 0; k < len[id]; ++k) { nxt[op] = tx[op] + f[dx] * table[id][k][0]; nxt[op ^ 1] = tx[op ^ 1] + f[dy] * table[id][k][1]; solver.insert(numrow, num[nxt[0]][nxt[1]]); } } } } } } } /********end********/ if (!solver.dance()) puts("No solution"); else for (rgi i = 1; i <= 10; ++i, puts("")) for (rgi j = 1; j <= i; ++j) putchar(ans[i][j]); return 0; }
练习¶
总结¶
DLX 能用来解决精确覆盖问题,适当地建立起模型后能解决一些大模拟。
References¶
- [1]英雄哪里出来 的 《夜深人静写算法(九)- Dancing Links X(跳舞链)》
- [2]万仓一黍 的 《跳跃的舞者,舞蹈链(Dancing Links)算法——求解精确覆盖问题》
- [3]zhangjianjunab 的 《DLX 算法一览》
- [4]静听风吟。的 《搜索:DLX 算法》
- [5]刘汝佳,陈锋 的 《算法竞赛进阶指南》
buildLast update and/or translate time of this article,Check the history
editFound smelly bugs? Translation outdated? Wanna contribute with us? Edit this Page on Github
peopleContributor of this article LeverImmy
translateTranslator of this article Visit the original article!
copyrightThe article is available under CC BY-SA 4.0 & SATA ; additional terms may apply.