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 article2024/10/9 22:38:42,Check the history
editFound smelly bugs? Translation outdated? Wanna contribute with us? Edit this Page on Github
peopleContributor of this article ouuan, leverimmy, iamtwz, Ir1d, 383494, LeverImmy, Tiphereth-A, CCXXXI, EarthMessenger, NachtgeistW, kenlig, opsiff, W-RJ, L1nkzz, Enter-tainer, StudyingFather, Chrogeek, SamZhangQingChuan
translateTranslator of this article Visit the original article!
copyrightThe article is available under CC BY-SA 4.0 & SATA ; additional terms may apply.