数独转Dacing-Links优化的精确覆盖问题

推荐博客传送门.

精确覆盖问题:

有一个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优化精确覆盖问题:

发现上面这个递归过程贼难写,且常会用到不用的点多次。

dancing-links精确的说是数据结构,是个双向十字循环链表。

如图所示(盗图狗路过):
这里写图片描述

递归的第一步是判断head的右指针是否指向自己,如果是,就出解了。

不然找到head的右指针指向的点的下指针指向的点。

然后用链表的方法去删除。

还原也是用链表的方法去还原。

注意删除时如果是从左到右,还原一定要从右到左,不要想当然以为没有关系。

效率:

dancing-links除了优化一下递归的缓存, 在空间和时间上没有任何优势。

所以我们需要加—优—化。

感性认识(前辈经验)告诉我们,每次找1的个数较少的行会快一点。

事实上它不是快了一点,在有解的时候你是无法想象这个优化的作用之大的。

还有一个优化我们搜出来是一个行的集合,它是无序的,

针对这个,可以把一开始格子就有数字的格子提前搞掉,这样减少了矩阵规模。

裸题:
JZOJ 4373. 【GDOI2016模拟】数独

16*16的数独,300ms+

Code:

#include<cstdio>
#include<cstring>
#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");
    }
}

猜你喜欢

转载自blog.csdn.net/cold_chair/article/details/81087511