Dancing Links是由Knuth提出的用于一类搜索问题的通用优化。
或称DLX。
主要应用于精确覆盖和重复覆盖。
精确覆盖题目:
POJ3740、POJ3074、POJ3076、HDU4069
重复覆盖题目:
HDU3529、HDU2295、POJ1084
关于DLX的详细介绍可以去查阅相关资料。
假设一个0-1矩阵,要求选择某几个行,使得所有的列均只有一个1(精确覆盖),或者至少有一个1(重复覆盖)。
DLX使用双向链表极大地优化了搜索。
DLX可以说是一种模板。应用于一类题目。只要把问题建模转化为一定的格式,则可以应用DLX。
精确覆盖应用于解数独、八皇后等。据说目前解数独速度最快的就是DLX。
重复覆盖应用于类似雷达覆盖的问题。
精确覆盖模板:
void remove(const int &c) { l[r[c]] = l[c]; r[l[c]] = r[c]; int i, j; for (i=d[c]; i!=c; i=d[i]) { for (j=r[i]; j!=i; j=r[j]) { u[d[j]] = u[j]; d[u[j]] = d[j]; s[ch[j]]--; } } } void resume(const int &c) { int i, j; for (i=u[c]; i!=c; i=u[i]) { for (j=l[i]; j!=i; j=l[j]) { s[ch[j]]++; u[d[j]] = j; d[u[j]] = j; } } l[r[c]] = c; r[l[c]] = c; } bool dfs(const int &k) { if (r[head]==head) { return true; } int ss = INT_MAX; int c; int i, j; for (i=r[head]; i!=head; i=r[i]) { if (s[i]<ss) { ss = s[i]; c = i; } } remove(c); for (i=d[c]; i!=c; i=d[i]) { o[k] = rh[i]; len = k; for (j=r[i]; j!=i; j=r[j]) remove(ch[j]); if (dfs(k+1)) return true; for (j=l[i]; j!=i; j=l[j]) resume(ch[j]); } resume(c); return false; }
重复覆盖模板:
void remove(int c) { int i; for (i=d[c]; i!=c; i=d[i]) { l[r[i]] = l[i]; r[l[i]] = r[i]; } } void resume(int c) { int i; for (i=u[c]; i!=c; i=u[i]) { l[r[i]] = r[l[i]] = i; } } int h() { memset(used, false, sizeof(used)); int c, i, j; int ret = 0; for (c=r[head]; c!=head; c=r[c]) { if (!used[c]) { ret++; used[c] = true; for (i=d[c]; i!=c; i=d[i]) { for (j=r[i]; j!=i; j=r[j]) { used[ch[j]] = true; } } } } return ret; } void dfs(int k) { if (k+h()>=len)//启发式搜索 return; if (r[head]==head) { len = k; return; } int ss = INT_MAX; int c, i, j; for (i=r[head]; i!=head; i=r[i]) { if (s[i]<ss) { ss = s[i]; c = i; } } for (i=d[c]; i!=c; i=d[i]) { remove(i); for (j=r[i]; j!=i; j=r[j]) remove(j); dfs(k+1); for (j=l[i]; j!=i; j=l[j]) resume(j); resume(i); } }
共用部分:
int new_node(int up, int down, int left, int right) { l[size] = left; r[size] = right; u[size] = up; d[size] = down; l[right] = r[left] = u[down] = d[up] = size; return size++; } void init(int n, int m) { size = 0; head = new_node(0, 0, 0, 0); len = n; int i; for (i=1; i<=m; i++) { new_node(i, i, l[head], head); ch[i] = i; s[i] = 0; } for (i=0; i<=n; i++) rh[i] = -1; } void insert_node(int i, int j) { ch[size] = j; s[j]++; if (rh[i]==-1) { rh[i] = new_node(j, d[j], size, size); } else { new_node(j, d[j], rh[i], r[rh[i]]); } }
DLX一般难在建模。只要建好模,一切都好办。
不过有些题目建模过程极其猥琐……比如POJ1084……
题解:
POJ3740:精确覆盖的基本题目。
POJ3074、POJ3076:数独的基本题目。把数组转化为01矩阵再进行DLX。
HDU4069:数独小变种,只是块的部分改变了,影响不大。
HDU3529:以空格为行,障碍为列,进行重复覆盖即可。
HDU2295:NC的我,由于一个字母打错,TLE了10次,然后精度问题WA了3次,还有一个CE,最终AC……二分雷达的半径进行重复覆盖,以雷达为行,以城市为列。
POJ1084:超恶心的一道题目。重复覆盖。以火柴为行,以方格为列。若某火柴支配某个方格(可多个方格),则标记为1。题目恶心在,方格的边长必须从小到大排列;如果从大到小排列的话,超时没话说。做这道题耗时n天,期间感冒……