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天,期间感冒……