Dancing Links 學習筆記

Dancing Links

本週的AI引論做業佈置了一道數獨ios

加了奇怪剪枝仍然TLE的Candy?不得不去學了dlx數組

dlxnb!數據結構

Exact cover

設全集X,X的若干子集的集合爲S。精確覆蓋是指,選擇一個S的子集S‘,知足X中的每個元素在S’中剛好出現一次。優化

是一個NPC問題。spa

能夠表示成01矩陣形式,選擇若干行,使得每一列剛好有且僅有一行爲1.指針

Sudoku

數獨能夠轉化爲精確覆蓋問題。code

令N=81爲數獨中格子個數,則:blog

  1. (x, y)=1表示(x,y)處填了數
  2. (x+N, z)=1表示x行填了z
  3. (y+N*2, z)=1表示y列填了z
  4. (r+N*3, z)=1表示r宮填了z

對於已經填了數的格子,轉化爲1行;遞歸

對於空的格子,轉化爲9行。ci

Algorithm X

一種顯然的dfs:

  • 就是選擇某一列,再選擇該列的爲1的某一行。
  • 刪除該列(包括該列上爲1的全部行)
  • 刪除該行(包括該行上爲1的全部列)

一個顯然的啓發式優化:minimum-remaining-values(MRV) heuristic

  • 優先選擇節點個數(1的個數)少的列。

Dancing links is the technique suggested by Donald Knuth to efficiently implement his Algorithm X.

是一種用來高效實現algorithm X的數據結構。

就是「交叉十字循環雙向鏈表」。

第0行分別是root和每一列的列首節點

其餘的只有爲1的位置纔有節點。

刪除某一列時,只要處理該列首節點(包括其左右節點)的左右指針;

刪除某列時同時要刪除該列上爲1的全部行;

刪除某一行時,只要處理該行全部節點(包括其上下節點)的上下指針。

值得注意的是,刪除以後該列/行的結構沒有改變。

dancing links

實現細節

每一個節點維護:

  • l r u d 左右上指針
  • col 列指針
  • row 行標號
  • cnt 保存該列的元素個數(只列首/用來MRV優化)

ah數組保存列首/行首節點指針

初始化init

  • 處理列首
  • 加在a[c]下,h[r]
  • (實際的「線」是否是直的不重要

刪除某列del

  • 刪除該列,以及該列上的全部行

恢復某列add

  • 按刪除相反的順序恢復

主過程dance

  1. root->r == root時完成

  2. 選擇元素最少的某列c並刪除該列(包括該列上爲1的全部行)

  3. 選擇該列上爲1的某行,刪除該行(包括該行上爲1的全部列)

    實際上這一行在2中已經刪除了,只要處理該行的列便可

  4. 遞歸搜索

  5. 恢復該行

  6. 恢復該列

==注意==

  1. del/add時處理個數是必要的,由於那一行所對應的列不必定會被刪去
  2. 恢復要按照刪除的逆序

代碼

POJ 3076 16*16數獨問題的代碼

結構體版太醜了仍是放指針版吧QwQ

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <ctime>
using namespace std;
const int NUM = 260*4*16, N = 260*16, K = 16, L = 4, M = N*16;

int n = 256*4, num = 256, m=0;
struct meow {
    meow *l, *r, *u, *d, *col;
    int row;
    int cnt;
} pool[NUM];
meow *a[NUM], *h[M], *root;
int ans[N], sz;
char s[20][20];
struct action {
    int x, y, z;
} q[M];

void init() {
    for(int i=0; i<=n; i++) a[i] = &pool[i];
    for(int i=0; i<=n; i++) {
        a[i]->l = a[i-1];
        a[i]->r = a[i+1];
        a[i]->u = a[i]->d = a[i];
        a[i]->col = a[i];
        a[i]->row = 0;
        a[i]->cnt = 0;
    }
    a[0]->l = a[n]; a[n]->r = a[0];
    root = a[0];
    sz = n;
    memset(h, 0, sizeof(h));
}
void link(int r, int c) {
    sz++;
    meow *x = a[sz] = &pool[sz];
    x->row = r;
    x->col = a[c];
    a[c]->cnt++;
    x->d = a[c]->d; x->d->u = x;
    x->u = a[c]; x->u->d = x;
    if(h[r] == NULL) {
        h[r] = x->l = x->r = x;
    }
    else {
        x->r = h[r]->r; x->r->l = x;
        x->l = h[r]; x->l->r = x;
    }
}
void del(meow *x) {
    x->l->r = x->r;
    x->r->l = x->l;
    for(meow *i = x->d; i != x; i = i->d)
        for(meow *j = i->r; j != i; j = j->r) {
            j->d->u = j->u;
            j->u->d = j->d;
            j->col->cnt--;
        }
}
void add(meow *x) {
    x->l->r = x->r->l = x;
    for(meow *i = x->u; i != x; i = i->u)
        for(meow *j = i->l; j != i; j = j->l) {
            j->u->d = j->d->u = j;
            j->col->cnt++;
        }
}
bool dance(int k) {
    if(root->r == root) {
        for(int i=1; i<=num; i++) {
            action &x = q[ans[i]];
            s[x.x][x.y] = 'A' + x.z-1;
        }
        return true;
    }
    meow *c = root; c->cnt = 1e9;
    for(meow *x = root->r; x != root; x = x->r)
        if(x->cnt < c->cnt) c = x;
    del(c);
    for(meow *i = c->d; i != c; i = i->d) {
        ans[k+1] = i->row;
        for(meow *j = i->r; j != i; j = j->r) del(j->col);
        if(dance(k+1)) return true;
        for(meow *j = i->l; j != i; j = j->l) add(j->col);
    }
    add(c);
    return false;
}
inline int grid_id(int x, int y, int k=L) {return (x-1)/k*k + (y-1)/k+1;}
void sudoku(int x, int y, int z) {
    m++;
    link(m, (x-1)*K+y);
    link(m, (x-1)*K+z + num);
    link(m, (y-1)*K+z + num*2);
    link(m, (grid_id(x, y)-1)*K+z + num*3);
    q[m] = (action) {x, y, z};
}
int main() {
    while(scanf("%s", s[1]+1) != EOF) {
        init();
        for(int i=1; i<=K; i++) {
            for(int j=1; j<=K; j++) {
                int a;
                if(s[i][j] == '-') a = 0;
                else a = s[i][j]-'A'+1;
                if(a != 0) sudoku(i, j, a);
                else for(int k=1; k<=K; k++) sudoku(i, j, k);
            }
            if(i != K) scanf("%s", s[i+1]+1);
        }
        dance(0);
        for(int i=1; i<=K; i++) {
            for(int j=1; j<=K; j++) printf("%c", s[i][j]);
            puts("");
        }
        puts("");
    }
}
相關文章
相關標籤/搜索