Loading

DLX 求解精确覆盖问题

DLX 实际上是用 Dancing Links 优化 X 算法。


精确覆盖问题

现在有一个 \(n \times m\)\(01\) 矩阵,要从中选出一些行,使得每一列都有且仅有一个 \(1\)

X 算法

这是一个搜索的算法。

我们以如下矩阵为例:

\[\begin{bmatrix} 1 & 0 & 1 & 1 \\ 1 & 0 & 1 & 0 \\ 0 & 1 & 0 & 1 \end{bmatrix} \]

首先,我们选第一行,然后第 \(1\)\(3\)\(4\) 列被选中了,那么我们观察一下这些列,对于第二行,第三行,显然其在这些列也有 \(1\),那么是不能选的,删去之后我们不能继续选择,但是第二列还没有被覆盖,那么不合法,回溯,选择第二行,此时再选择第三行即可。

详细过程可以参考 OI-wiki 上对于 X 算法过程的解释

因为 X 算法有大量的实现删除一列,恢复一列的操作,所以我们考虑用一种数据结构来优化。

Dancing Links 本质上是一个双向的十字循环链表(据说是因为指针在链表间跳动像极了优美的舞蹈而得名),我们需要实现一些操作。

十字链表的具体结构可以参考 OI-wiki 的图解

remove

删除一列,设为 \(c\)

把 c 左右合并。

向下遍历,对于每一行合并上下。

recover

复原一列,和 remove 完全相反的操作。

build

建立每一列的指示指针即可。

insert

设插入的点为 \(x\)

那么把 \(x\) 插到这一列的指示指针下面,\(\text{first}\) 的右边即可,这一行还没有元素的话,就令 \(\text{first} = x\)

dance

是一个搜索函数。

int dance(int dep) {
    if (!R[0]) {
        Ans = dep;
        return 1;
    }
    int c = R[0];
    for (int i = R[0]; i != 0; i = R[i])
        if (siz[i] < siz[c]) c = i; // 这一步是为了让搜索树的分叉更小。
    remove(c);
    for (int i = D[c]; i != c; i = D[i]) {
        stk[dep] = row[i];
        for (int j = R[i]; j != i; j = R[j]) remove(col[j]);
        if (dance(dep + 1)) return 1;
        for (int j = L[i]; j != i; j = L[j]) recover(col[j]); // 回溯
    }
    recover(c); // 回溯
    return 0;
}

建模

以数独问题为例。

我们把在 \((x, y)\) 填了 \(z\) 看作是一行,那么这会覆盖第 \(x\) 行的 \(z\),覆盖第 \(y\) 列的 \(z\),覆盖某个宫的 \(z\),以及 \((x, y)\) 这个位置,比较容易漏掉的是最后的覆盖一个位置。

建模后直接 dance 即可,注意 build 的时候大小不能乱开,要精确的开!。

代码模板

#include <bits/stdc++.h>
#define int long long

#define F(i, a, b) for (int i = (a); i <= (b); i++)
#define dF(i, a, b) for (int i = (a); i >= (b); i--)
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 200005, M = (N << 1), inf = 1e16, mod = 1e9 + 7;
struct DLX {
    int n, m, L[N], R[N], U[N], D[N], tot, col[N], row[N], fi[N], siz[N], Ans, stk[N];
    void build(int r, int c) {
        n = r, m = c;
        for (int i = 0; i <= c; i++) {
            L[i] = i - 1, R[i] = i + 1;
            U[i] = D[i] = i;
        }
        L[0] = c, R[c] = 0, tot = c;
        memset(fi, 0, sizeof fi);
        memset(siz, 0, sizeof siz);
    }
    void insert(int x, int y) {
        col[++tot] = y, row[tot] = x, siz[y]++;
        D[tot] = D[y]; U[D[y]] = tot; U[tot] = y; D[y] = tot;
        if (!fi[x]) {
            L[tot] = R[tot] = fi[x] = tot;
        } else {
            L[R[fi[x]]] = tot; R[tot] = R[fi[x]]; R[fi[x]] = tot; L[tot] = fi[x];
        }
    }
    void remove(int c) {
        L[R[c]] = L[c], R[L[c]] = R[c];
        for (int i = D[c]; i != c; i = D[i])
            for (int j = R[i]; j != i; j = R[j])
                U[D[j]] = U[j], D[U[j]] = D[j], siz[col[j]]--;
    }
    void recover(int c) {
        for (int i = U[c]; i != c; i = U[i])
            for (int j = L[i]; j != i; j = L[j]) 
                U[D[j]] = D[U[j]] = j, siz[col[j]]++;
        L[R[c]] = R[L[c]] = c;
    }
    int dance(int dep) {
        if (!R[0]) {
            Ans = dep;
            return 1;
        }
        int c = R[0];
        for (int i = R[0]; i != 0; i = R[i])
            if (siz[i] < siz[c]) c = i;
        remove(c);
        for (int i = D[c]; i != c; i = D[i]) {
            stk[dep] = row[i];
            for (int j = R[i]; j != i; j = R[j]) remove(col[j]);
            if (dance(dep + 1)) return 1;
            for (int j = L[i]; j != i; j = L[j]) recover(col[j]);
        }
        recover(c);
        return 0;
    }
} solver;
signed main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    solver.build(n, m);
    F(i, 1, n) {
        F(j, 1, m) {
            int x; cin >> x;
            if (x) solver.insert(i, j);
        }
    }
    solver.dance(1);
    if (solver.Ans) {
        F(i, 1, solver.Ans - 1) {
            cout << solver.stk[i] << ' ';
        }
    } else {
        cout << "No Solution!\n";
    }
    return 0;
}

posted @ 2024-07-12 11:00  紊莫  阅读(46)  评论(0)    收藏  举报