DLX算法

本文介绍DLX算法,它由Knuth教授发明,用于解决精确覆盖问题,采用双向循环十字链表数组存储01矩阵。文中通过舞蹈链、数独、智慧珠游戏等例子展示其应用,还提及重复覆盖问题及解决方法,如HDU2295雷达问题用二分法结合DLX求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


marp: true
headingDivider: true
theme: default
backgroundColor: #db8
header: 中山市迪茵公学
footer: “2024.2.20”
paginate: true
style: |
section {
font-family: STKaiti, sans-serif;
font-size: 25px;
}

h1 {
    font-family: 宋体, sans-serif;
    font-size: 40px;
}

h2 {
    font-family: 宋体, sans-serif;
    font-size: 35px;
}

h3 {
    font-family: 宋体, sans-serif;
    font-size: 30px;
}
h4 {
    font-family: 楷体, sans-serif;
    font-size: 25px;
}

DLX 算法

DLX的全称为“Dancelinks X”,即使用舞蹈链“Dance links”实现的X算法。X算法是由图灵奖获得者、计算机理论大师Knuth教授发明的,主要用于解决一类精确覆盖问题的算法。这个算法采用“Dance link”来实现,有着极高的效率。
精确覆盖问题是指这样一类问题:给一个全集 S S S,求出某些子集,使得这些子集没有交集,而其并集等于全集 S S S.
通常可以用 01 01 01矩阵来表示精确覆盖问题。
给一个 n × m n \times m n×m 01 01 01矩阵,每一行代表一个子集,每一列表示某个元素。如果第 i i i行表示的子集包含第 j j j个元素,则 01 01 01矩阵的第 i i i行第 j j j列为 1 1 1.

你要选择若干行,使得这些行包含所有的元素,且没有两行包含相同元素。

基本上所有的数独问题能转化为精确覆盖问题,从而可以快速求解。


精确覆盖问题

01 01 01矩阵,用一个双向循环十字链表数组来存储。

struct node{
    int val, row, col, lp, rp, up, dn;
};

链表的每个元素,可以很方便的访问其上下左右相邻的元素。

0 0 0行看作是列头元素,如果其中第 i i i个元素被删除,就意味着第 i i i列被删除;

其他的元素被删除,就只是代表它自己被删除。

因为是双向链表,删除的元素实际上还在那里,只是其前驱的后继和其后继的前驱都不再指向它了。重新恢复是很容易的,因为它还保留着指向前驱和后继的指针,可以很快恢复。


具体实现过程

假设 01 01 01矩阵有 n n n m m m列。
我们要选择一个行的集合,使得其中每一列都有 1 1 1.

一、如何设计十字链表呢?

1、 设计第 0 0 0

  • 0 0 0行有 m + 1 m+1 m+1个元素,其中第 0 0 0个元素表示整个表头,当它的左右指针为空时,代表十字链表为空。第 0 0 0行的每个元素都有左右指针,分别指向该行中它左边的元素和右边的元素。第 0 0 0个元素的左指针指向第 m m m个元素,第 m m m个元素的右指针指向第 0 0 0个元素。

  • 后面的 m m m个元素,每个元素都是某一列的列头,每个元素都有一个上指针和一个下指针。分别指向该列中的上一个元素和下一个元素。初始时,它们都指向自己,每插入一个元素后,都需要修改相应的指针。


假设有一个 6 × 6 6 \times 6 6×6 01 01 01矩阵:

     [ 0 1 1 0 0 0 1 0 1 0 0 1 0 1 0 1 1 1 1 0 0 0 0 1 0 0 0 1 1 0 0 1 0 1 0 1 ] \:\: \: \: \begin{aligned} \begin{bmatrix} &0 &1 &1 &0 &0 &0 & \\ &1 &0 &1 &0 & 0 &1 \\ &0 &1 & 0 & 1 & 1 & 1 \\ &1 &0 &0 &0 & 0 &1 \\ &0 &0 &0 &1 &1 &0 \\ &0 &1 &0 &1 &0 &1 \end{bmatrix} \end{aligned} 010100101001110000001011001010011101

开始时,设计第 0 0 0行的链表如下图所示:
在这里插入图片描述


接下来将 01 01 01 矩阵的第一行加入十字链表中。

0 1 1 0 0 0

图形做了一点简化,所有的连接首尾的箭头都用单向箭头,前方没有节点。

在这里插入图片描述


接下来再将 01 01 01矩阵的第二行加入十字链表中。

1 0 1 0 0 1

此时十字链表变成:
在这里插入图片描述


再加入第三行:

 0 1 0 1 1 1 

此时十字链表变成下图:
在这里插入图片描述


相信大家应该能够明白这个十字链表的建立过程了吧。
最终十字链表是这样的:

在这里插入图片描述


首先从列着手,先选中某一列,将它从列头(第 0 0 0行)的链表中删除,然后,遍历该列,将该列为 1 1 1的行删除,此处只删除该行其他位置在其列中的存在关系,当前列所有 1 1 1的位置的上下关系并不需要删除,因为该列已经整体在列头中删除掉了。

以上操作可以作为一个基本操作单元,命名为remove©。

然后我们再从第 c c c列中,依次尝试选择某个为 1 1 1的位置所在的行(一定要选择一行,但是并不知道最终应该选择哪一行,所以每一行都要尝试),将该行中其他位置的 1 1 1所在的列再来进行remove操作。

重复以上的操作,直到列头(即第 0 0 0行只剩下元素 0 0 0,此时表示所有的列都已经被删掉了)。于是之前的选择的行就是我们的答案。

每次我们 r e m o v e ( c ) remove(c) remove(c)时,可以选择 1 1 1最少的列操作,这样可以极大的加快速度。道理很显然,即尽量减少dfs搜索树的大小。


例1 舞蹈链(DLX)

题目描述

给定一个 N N N M M M 列的矩阵,矩阵中每个元素要么是 1 1 1,要么是 0 0 0
你需要在矩阵中挑选出若干行,使得对于矩阵的每一列 j j j,在你挑选的这些行中,有且仅有一行的第 j j j个元素为 1 1 1
如果有多种选择方案,请任意输出一种方案。

数据范围

对于 100 % 100\% 100% 的数据, N , M ≤ 500 N,M\leq 500 N,M500,保证矩阵中 1 1 1 的数量不超过 5000 5000 5000 个。


分析:这是一道模板题

#define maxn 505
#define maxt 250005
int arr[maxn][maxn];
int n, m, tot;
int chead[maxn], rhead[maxn], up[maxt], down[maxt], lt[maxt], rt[maxt], cid[maxt], rid[maxt];
int selrow[maxn], cnt[maxn];

void remove(int c){
    rt[lt[c]] = rt[c], lt[rt[c]] = lt[c];
    for(int i = down[c]; i != c; i = down[i]){
        for(int j = rt[i]; j != i; j = rt[j]){
            up[down[j]] = up[j], down[up[j]] = down[j];
            cnt[cid[j]]--;
        }
    }
}

void reset(int c){
    for(int i = up[c]; i != c; i = up[i]){
        for(int j = lt[i]; j != i; j = lt[j]){
            up[down[j]] = j, down[up[j]] = j;
            cnt[cid[j]]++;
        }
    }
    rt[lt[c]] = c, lt[rt[c]] = c;
}

bool dfs(){
    if(rt[0] == 0){
        for(int i = 1; i <= n; i++){
            if(selrow[i] == 1) printf("%d ", i);
        }
        printf("\n");
        return 1;
    }
    int minz = maxn, col = 0;
    for(int i = rt[0]; i != 0; i = rt[i]){
        if(cnt[i] < minz)minz = cnt[i], col = i;
    }
    if(minz == 0) return 0;
    remove(col);
    for(int i = down[col]; i != col; i = down[i]){
        selrow[rid[i]] = 1;
        for(int j = rt[i]; j != i; j = rt[j])remove(cid[j]);
        if(dfs())return 1;
        for(int j = lt[i]; j != i; j = lt[j]) reset(cid[j]);
        selrow[rid[i]] = 0;
    }
    reset(col);
    return 0;
}

int main(){
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            scanf("%d", &arr[i][j]);
        }
    }
    for(int i = 0; i <= m; i++){
        up[i] = down[i] = i;
        lt[i] = i - 1;
        rt[i] = i + 1;
        chead[i] = i;
    }
    lt[0] = m, rt[m] = 0;
    tot = m;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            if(arr[i][j] == 1){
                tot++;
                cid[tot] = j, rid[tot] = i;
                if(rhead[i] == 0){
                    lt[tot] = rt[tot] = tot;
                }
                else{
                    lt[tot] = rhead[i], rt[tot] = rt[rhead[i]];
                    rt[rhead[i]] = tot, lt[rt[tot]] = tot;
                }
                rhead[i] = tot;
                up[tot] = chead[j], down[tot] = down[chead[j]];
                down[chead[j]] = tot, up[down[tot]] = tot;
                chead[j] = tot;
                cnt[j]++;
            }
        }
    }
    if(dfs() == 0) printf("No Solution!\n");
    return 0;
}

例2、 数独问题

数独问题怎么转化为精确覆盖问题?

在以下的每个空格中填入一个 1 ∼ 9 1 \sim 9 19的整数,使得每一行、每一列、每个九宫格中 1 ∼ 9 1 \sim 9 19都刚好出现一次。

在这里插入图片描述


数独问题

数独问题转化为精确覆盖问题

一、分析:
对于上述 9 × 9 9 \times 9 9×9数独问题,第 i i i行、第 j j j列填的数为 k k k, 一共有 729 729 729情况,从中选择出来 81 81 81种情况,要求这 81 81 81种情况要满足一些约束条件,即每行不能有相同的数,每列不能有相同的数,每个九宫格中不能有相同的数,每行每列只能填一个数。


例2、数独问题

数独问题转化为精确覆盖问题

二、建模
建立一个 729 729 729 324 324 324列的 01 01 01矩阵,行代表所有行为,列代表所有约束。

具体来讲:在九宫格的第 i i i行第 j j j列写上数字 k k k,这一行为对应于 01 01 01矩阵的第 ( i − 1 ) × 81 + ( j − 1 ) × 9 + k (i-1) \times 81+(j-1) \times 9+k (i1)×81+(j1)×9+k行。

01 01 01矩阵一共有 324 324 324列,分成 4 4 4类,每类 81 81 81列。
第一类表示某行是否出现某值,共81种;第二类表示某列是否出现某值,共 81 81 81种,第三类表示某宫中是否出现某个值,共 81 81 81种,第四类表示某行某列已填好,共 81 81 81种。

对于每一种行为,比如在第 i i i行第 j j j列中填上了数字 k k k, 则先找到对应的行,然后将第一类中第 ( i − 1 ) × 9 + k (i-1) \times 9 + k (i1)×9+k列设为 1 1 1, 将第二类中第 ( j − 1 ) × 9 + k (j-1) \times 9+k (j1)×9+k列设为 1 1 1,将第三类中第 g × 9 + k g \times 9+k g×9+k列设为 1 1 1,其中 g = ( i − 1 ) / 3 × 3 + ( j − 1 ) / 3 + 1 g=(i-1)/3\times 3+(j-1)/3+1 g=(i1)/3×3+(j1)/3+1,表示在第几个 3 × 3 3\times 3 3×3的宫中;将第四类的第 ( i − 1 ) × 9 + j (i-1) \times 9 + j (i1)×9+j设为 1 1 1.


数独问题

现在 9 × 9 9 \times 9 9×9的数独问题已经转化为了 729 × 324 729 \times 324 729×324 01 01 01矩阵上的精确覆盖问题了。

每行刚好 4 4 4 1 1 1,一共 324 324 324列,刚好要选择 81 81 81行,才有可能保证每一列中都有 1 1 1,且每一列中刚好一个 1 1 1.

列的前3类中,每一列中都只能出现一个 1 1 1,刚好对应于九宫格的约束条件,即每行、每列、每宫中都能出现某个值 1 1 1次。

最后一类的列中,只能出现一个 1 1 1, 对应于每行每列只能填一个数。

比如第一类中的第一列的 1 1 1,表示九宫格的第 1 1 1行出现了 1 1 1, 第二列中的 1 1 1,表示九宫格的第 1 1 1行出现了 2 2 2


参考代码

以下为用到的数组的定义

int matrix[maxn][maxc]; //转换后的01矩阵
int arr[20][20]; //原九宫格
int cascnt, row, col; 
int chead[maxc], rhead[maxn]; //chead[i]表示第i列最新加入链表的元素,rhead[i]表示第i行最新加入列表的元素。
int up[maxt], down[maxt], lp[maxt], rp[maxt]; //上下左右指针
int rid[maxt], cid[maxt], cnt[maxc]; //十字链表中每个元素对应的行号和列号 
int tot, rowcnt, colcnt; //tot:十字链表中的元素个数,rowcnt:十字链表的行数,colcnt:十字链表的列数 
bool sel[maxn];  //某行是否选中 
int ans_r, ans_c, ans_x; //
bool haveans;
int n;

参考代码

以下为模型转换的代码:

void convert(int n){
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            for(int k = 1; k <= n; k++){
                if(arr[i][j] != 0 && arr[i][j] != k) continue;
                row = (i - 1) * (n * n) + (j - 1) * n + k;
                for(int cas = 1; cas <= cascnt; cas++){
                    for(int ii = 1; ii <= n; ii++){
                        for(int jj = 1; jj <= n; jj++){
                            col = (cas - 1) * (n * n) + (ii - 1) * n + jj;
                            if(cas == 1) if(ii == i && jj == k) matrix[row][col] = 1;
                            if(cas == 2) if(ii == j && jj == k) matrix[row][col] = 1;
                            if(cas == 3) if(ii == (i - 1) / 3 * 3 + (j - 1) / 3 + 1 && jj == k) matrix[row][col] = 1;
                            if(cas == 4) if(ii == i && jj == j) matrix[row][col] = 1;
                        }
                    }
                }
            }
        }
    }
}

以上模型转换代码比较好理解,但是比较慢。实际上可以简化为只用四层循环,速度会快很多:

void convert() {
    row = 0, col = 0;
    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <= 9; j++) {
            for (int k = 1; k <= 9; k++) {
                if (arr[i][j] != 0 && arr[i][j] != k)
                    continue;
                row++;
                i_[row] = i, j_[row] = j, k_[row] = k; //当前行对应的i,j,k记录下来
                for (int cas = 1; cas <= 4; cas++) {
                    if (cas == 1)
                        col = (i - 1) * 9 + k;
                    if (cas == 2)
                        col = 81 + (j - 1) * 9 + k;
                    if (cas == 3)
                        col = 162 + ((i - 1) / 3 * 3 + (j - 1) / 3) * 9 + k;
                    if (cas == 4)
                        col = 243 + (i - 1) * 9 + j;
                    matrix[row][col] = 1;
                }
                if (arr[i][j] == k)
                    break;
            }
        }
    }
}

参考代码

以下为建十字链表的代码


void init(){
    for(int i = 0; i <= colcnt; i++) {
        up[tot] = tot, down[tot] = tot, lp[tot] = tot - 1, rp[tot] = tot + 1;
        chead[i] = tot;
        cid[tot] = i, rid[tot] = 0;
        ++tot;
    }
    rp[tot - 1] = 0, lp[0] = tot - 1, rp[0] = 1;
    for(int i = 1; i <= rowcnt; i++){
        for(int j = 1; j <= colcnt; j++){
            if(matrix[i][j] == 1){
                cid[tot] = j, rid[tot] = i;
                if(rhead[i] == 0){
                    rhead[i] = tot;
                    lp[tot] = tot, rp[tot] = tot; 
                }
                else{
                    lp[tot] = rhead[i], rp[tot] = rp[rhead[i]];
                     lp[rp[rhead[i]]] = tot, rp[rhead[i]] = tot;
                    rhead[i] = tot;
                }
                up[tot] = chead[j], down[tot] = down[chead[j]], up[down[chead[j]]] = tot, down[chead[j]] = tot;
                chead[j] = tot;
                cnt[j]++;
                ++tot;
            }
        }
    }
}

参考代码

以下为删除某列和恢复某列的代码:

void remove(int col){
    lp[rp[col]] = lp[col], rp[lp[col]] = rp[col]; 
    for(int i = down[col]; i != col; i = down[i]){
        for(int j = rp[i]; j != i; j = rp[j]) up[down[j]] = up[j], down[up[j]] = down[j], cnt[cid[j]]--;
    }
}

void reset(int col){
    for(int i = up[col]; i != col; i = up[i]){
        for(int j = lp[i]; j != i; j = lp[j]) up[down[j]] = j, down[up[j]] = j, cnt[cid[j]]++;
    }
     rp[lp[col]] = col, lp[rp[col]] = col;
}

参考代码

以下为dfs的代码,即在十字链表中找出最少的行的集合,保证能够覆盖所有的列。

bool dfs(){
    if(rp[0] == 0){
        for(int i = 1; i <= rowcnt; i++){
            if(sel[i] == 1) {
                ans_r = (i - 1) / (n * n) + 1;
                ans_c = (i - 1) % (n * n) / n + 1;
                ans_x = (i - 1) % n + 1;
                arr[ans_r][ans_c] = ans_x;
            }
        }
        return 1;
    }
    int maxz = INF, s_col = 0;
    for(int i = rp[0]; i != 0; i = rp[i]){
        if(cnt[i] < maxz) maxz = cnt[i], s_col = i;
    }
    remove(s_col);
    for(int i = down[s_col]; i != s_col; i = down[i]){
        sel[rid[i]] = 1;
        for(int j = rp[i]; j != i; j = rp[j])remove(cid[j]);
        if(dfs()) return 1;
        for(int j = lp[i]; j != i; j = lp[j]) reset(cid[j]);
        sel[rid[i]] = 0;
    }
    reset(s_col);
    return 0;
}


int main(){
    n = 9;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            scanf("%d", &arr[i][j]);
        }
    }
    cascnt = 4;
    rowcnt = n * n * n;
    colcnt = n * n * cascnt; 
    convert(n);
    init();
    bool haveans = dfs();
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

例3 智慧珠游戏

题目描述

智慧珠游戏拼盘由一个三角形盘件和 12 个形态各异的零件组成。拼盘和零件如图1所示:
在这里插入图片描述
在这里插入图片描述


对于由珠子构成的零件,可以放到盘件的任一位置,条件是能有地方放,且尺寸合适,所有的零件都允许旋转(0º、90º、180º、270º)和翻转(水平、竖直)。

现给出一个盘件的初始布局,求一种可行的智慧珠摆放方案,使所有的零件都能放进盘件中。


以下为一种方案:
在这里插入图片描述

在这里插入图片描述
方案保证唯一


分析

模型转换

对于12个零件,我们可以选择其左上方的一个珠子设为参考点 ( 0 , 0 ) (0,0) (0,0),求出其他珠子的相对坐标。
因为零件可以旋转、翻转等,相当于坐标值交换,取反等操作的组合,共有8种。即 ( x , y ) , ( x , − y ) , ( − x , y ) , ( − x , − y ) , ( y , x ) , ( − y , x ) , ( y , − x ) , ( − y , − x ) (x,y),(x,-y),(-x,y),(-x,-y),(y,x),(-y,x),(y,-x),(-y,-x) (x,y),(x,y),(x,y),(x,y),(y,x),(y,x),(y,x),(y,x).

相当于每个零件,最多有8个不同的变种。
每个零件的每个变种,其原点可以放到每个位置,所以对应的行数最多为 55 × 12 × 8 55 \times 12 \times 8 55×12×8。但实际上要不了这么多行。
55 55 55个位置都需要放智慧珠,表示该位置必须被占用。我们不用管该位置放的那种零件,只要有零件,就设置为1,所以需要55列作为约束条件。同时,每种零件都必须出现一次,共12种零件,所以再增加12列,出现了该零件,则标记为1。一共需要67列。
这样就转换为一个 01 01 01矩阵了。

01 01 01矩阵的每一行,我们要记录一下它对应的是哪个零件。
01 01 01的前 55 55 55列,我们记录它对应于棋盘的哪一行哪一列。


重复覆盖问题

重复覆盖问题是指选择满足条件的若干子集,要求覆盖全集,但允许子集有交集, 选择最少数量的子集,或者符合某些属性的子集等。

如果用 01 01 01矩阵的行来表示集合,列表示元素, 则重复覆盖问题指的是选择若干行,使得每一列都至少有一个 1 1 1.

重复覆盖因为行与行之间不再存在互斥关系,所以不能因为选了某一行,而将其关联行全部删除。


具体做法如下:

  • 选择某列,让该列消失:除了当前行的 1 1 1之外,该列上其他的 1 1 1的左右节点都断开与它的联系。
  • 选择该列上某个 1 1 1所在的行,将该行上的 1 1 1所在的列消失:每列除了当前行的 1 1 1之外, 该列其他的 1 1 1的左右节点都断开与它的联系。
  • 再配合一个 A ∗ A* A剪枝,即利用一个估价函数来剪枝,加快搜索。剪枝搜索一般为剩下的 1 1 1通过精确覆盖还至少需要选择多少行,设估价函数的值为 h h h, 当前已经选择的行数为 k k k, 如果 k + h > a n s k+h > ans k+h>ans, 则认为当前局面搜索下去,不可能出现最优解(假设是最少的行数),可以提前回溯。

第二步要留当前行的 1 1 1不删除,一是因为这个 1 1 1的存在,不会导致再次搜索到该列;二是因为未来的恢复操作需要这些 1 1 1来做线索。


例4 HDU2295 雷达

题目描述

爪哇王国的 N N N个城市需要被雷达覆盖,因为王国有 M M M个雷达站,但只有 K K K个操作员,所以我们最多只能操作 K K K个雷达。所有雷达的覆盖范围都是半径为 R R R的圆形。我们的目标是在使用不超过 K K K个雷达的情况下,尽量减小 R R R,同时覆盖整个城市。

数据范围

1 ≤ T ≤ 20 , 1 ≤ N , M ≤ 50 , 1 ≤ K ≤ M , 0 ≤ X , Y ≤ 1000 1 ≤ T ≤ 20,1 ≤ N, M ≤ 50,1 ≤ K ≤ M,0 ≤ X, Y ≤ 1000 1T20,1N,M50,1KM,0X,Y1000


分析

二分雷达覆盖的半径。
求出该半径下每个雷达覆盖哪些城市,建立01矩阵。行表示雷达,列表示城市,如果雷达i能够覆盖城市j,则矩阵的第 i i i行第 j j j列设为1.
然后用DLX求重复覆盖,看能否满足要求。如果可以满足,则继续缩小半径;否则增大半径。


struct DLX {

    const static int ROW = 1003, COL = 1003, SIZE = ROW * COL;
    int L[SIZE], R[SIZE], U[SIZE], D[SIZE];  //模拟指针
    int col[SIZE], row[SIZE]; //所在列 所在行
    int visn, visited[COL]; //用于估价函数
    int sel[ROW], seln; //选择的行
    int sz[COL]; //列元素数
    int total/*节点编号*/, H[ROW];

    void init(int clen) { //初始化列头指针
    for(int i = 0; i <= clen; ++i) {
        L[i] = i - 1; R[i] = i + 1;
        U[i] = D[i] = i; sz[i] = 0;
    }
    for(int i=0;i<ROW;i++) H[i]=-1;
    for(int i=0;i<COL;i++) visited[i]=0; visn = 0; //用于重复覆盖的A*剪枝
    L[0] = clen; R[clen] = 0; total = clen + 1;
    }


参考代码


void _remove(const int &c) {
    for(int i = D[c]; i != c; i = D[i])
        L[R[i]] = L[i], R[L[i]] = R[i];
}

void _resume(const int &c) {
    for(int i = U[c]; i != c; i = U[i])
        L[R[i]] = R[L[i]] = i;
}

参考代码

int Astar(){ //估价函数
    int res = 0; ++visn;
    for(int i = R[0]; i != 0; i = R[i]) {
        if(visited[i] != visn) {
        ++res; visited[i] = visn;
        for(int j = D[i]; j != i; j = D[j])
            for(int k = R[j]; k != j; k = R[k])
                visited[col[k]] = visn;
        }
    } return res;
}

参考代码


bool _dance(int now){
    if(now + Astar() > limit) return false;
    if(R[0] == 0) return now<=limit;
    int c=R[0], i, j;
    for(i = R[0]; i != 0; i = R[i]) //选择元素最少的列c
        if(sz[c] > sz[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);
        if(_dance(now + 1)) { //对于不同的题 这个地方常常需要改动
            return true;
        }
        for(j = L[i]; j != i; j = L[j])
            _resume(j);
        _resume(i);
    }
    return false;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值