推荐博客传送门.
有一个n行m列的01矩阵。
你要选一些行,使得每一列都有且仅有一个1。
首先数独有四个限制:
1.每个格子能填也只能填一个数字。
2.每一行要填满九个数字。
3.每一列要填满九个数字。
4.每一宫要填满九个数字。
格子有9*9=81个,行列宫分别也有9个,每个要9个数字,所以一共有9*9*4列。
行的话也很简单。
一共9*9个格子,每个有9种填法,一共有9*9*9行。
*如果一个格子上已经有数字了,只往那个数字对应的一行加1。
显然就是枚举行,找到行上有1的位置,找到有1的位置对应列,找到列上有1的位置,删掉整行。
然后变成子精确覆盖问题。
发现上面这个递归过程贼难写,且常会用到不用的点多次。
dancing-links精确的说是数据结构,是个双向十字循环链表。
递归的第一步是判断head的右指针是否指向自己,如果是,就出解了。
不然找到head的右指针指向的点的下指针指向的点。
然后用链表的方法去删除。
还原也是用链表的方法去还原。
注意删除时如果是从左到右,还原一定要从右到左,不要想当然以为没有关系。
dancing-links除了优化一下递归的缓存, 在空间和时间上没有任何优势。
所以我们需要加—优—化。
感性认识(前辈经验)告诉我们,每次找1的个数较少的行会快一点。
事实上它不是快了一点,在有解的时候你是无法想象这个优化的作用之大的。
还有一个优化我们搜出来是一个行的集合,它是无序的,
针对这个,可以把一开始格子就有数字的格子提前搞掉,这样减少了矩阵规模。
裸题:
JZOJ 4373. 【GDOI2016模拟】数独
16*16的数独,300ms+
Code:
#include
#include
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define g 15
using namespace std;
const int N = 16 * 16 * 16 + 17, M = 16 * 16 * 4 + 17;
struct node {
int l, r, u, d, row, col;
} a[5001000];
int n, m, cur, head, bz[N][M], cnt[M], ans[N];
void add(int x, int y, int z) {
int cur = (x * 16 + y) * 16 + z;
bz[cur][16 * x + y] = 1;
bz[cur][256 + 16 * x + z] = 1;
bz[cur][512 + 16 * y + z] = 1;
bz[cur][768 + (x / 4 * 4 + y / 4) * 16 + z] = 1;
}
void remove(int col) {
a[a[col].l].r = a[col].r; a[a[col].r].l = a[col].l;
for(int i = a[col].d; i != col; i = a[i].d)
for(int j = a[i].r; j != i; j = a[j].r) {
cnt[a[j].col] --;
a[a[j].d].u = a[j].u; a[a[j].u].d = a[j].d;
}
}
void recall(int col) {
for(int i = a[col].u; i != col; i = a[i].u)
for(int j = a[i].l; j != i; j = a[j].l) {
cnt[a[j].col] ++;
a[a[j].d].u = j; a[a[j].u].d = j;
}
a[a[col].l].r = col; a[a[col].r].l = col;
}
int dg() {
if(a[head].r == head) return 1;
int curcol, mt = n + 1;
for(int i = a[head].r; i != head; i = a[i].r)
if(cnt[i] < mt) {
curcol = i, mt = cnt[i];
if(mt == 1) break;
}
remove(curcol);
for(int i = a[curcol].d; i != curcol; i = a[i].d) {
for(int j = a[i].r; j != i; j = a[j].r) remove(a[j].col);
ans[a[i].row] = 1;
if(dg()) return 1;
ans[a[i].row] = 0;
for(int j = a[i].l; j != i; j = a[j].l) recall(a[j].col);
}
recall(curcol);
return 0;
}
int main() {
fo(i, 0, g) {
fo(j, 0, g) {
char ch = ' ';
for(; (ch < 'A' || ch > 'Z') && ch != '-'; ch = getchar());
if(ch == '-') {
fo(k, 0, g) add(i, j, k);
} else add(i, j, ch - 'A');
}
}
n = 16 * 16 * 16, m = 16 * 16 * 4;
cur = m + 1; head = m;
fo(i, 0, m) {
a[i].l = i - 1; a[i].r = i + 1;
a[i].u = a[i].d = i;
a[i].col = i; a[i].row = 0; cnt[i] = 0;
}
a[0].l = m; a[m].r = 0;
fo(i, 0, n - 1) {
int st = cur, la = cur;
fo(j, 0, m - 1) if(bz[i][j]) {
a[cur].u = a[j].u; a[a[j].u].d = cur;
a[cur].d = j; a[j].u = cur;
a[cur].col = j; cnt[j] ++;
a[cur].l = la; a[la].r = cur;
a[cur].r = st; a[st].l = cur;
a[cur].row = i;
la = cur ++;
}
}
dg();
fo(i, 0, g) {
fo(j, 0, g) {
fo(k, 0, g) if(ans[(i * 16 + j) * 16 + k])
printf("%c", 'A' + k);
}
printf("\n");
}
}