hihoCoder #1321 : 搜索五?数独 (Dancing Links ,精确覆盖)

hiho一下第102周的题目。

原题地址:http://hihocoder.com/problemset/problem/1321

题意:输入一个9*9数独矩阵,0表示没填的空位,输出这个数独的答案。

提示已经讲解的很清楚了。稍微整理下思路。最后附AC代码。

一、Dancing Links解决精确覆盖问题。

     1.精确覆盖问题

        给定一个n行,m列的01矩阵。从中选择若干行使得每一列有且恰好只有一个1

例如:

答案是选择2,3,4行。

2.DancingLinks求解精确覆盖问题

精确覆盖问题:从01矩阵中选择若干行使得每一列有且恰好只有一个1。

简单来说

精确覆盖问题的算法就是利用DFS搜索

先选择一行,将当前覆盖的列以及能够覆盖到该列的所有行全部去掉,形成新的小规模的矩阵。

然后再逐行枚举,添加新的要覆盖的列,删除相应的行和列,重复直到剩下的矩阵只有1行。

如果剩下最后一行都是1,问题就解决了。

如果剩下最后一行还有0,则存在某些列没有被覆盖到。

DFS回溯求解。

而DancingLinks其实是一种数据结构。

不懂DangcingLinks的戳这里:http://www.cnblogs.com/grenet/p/3145800.html

DancingLinks模板(真不记得从哪里抄的):

/*********************************************************************************
                      DLX模板 精确覆盖(exact_) 重复覆盖(repeat_)
**********************************************************************************/
struct DLX
{
    int n,m,SIZE;
    int U[maxnode],D[maxnode],R[maxnode],L[maxnode],Row[maxnode],Col[maxnode];//L,R,D,U四个数组记录某节点上下左右邻居
    int H[MaxN], S[MaxM];//H记录排头,S记录某列有多少个节点
    int ansd, ans[MaxN];
    void init(int _n,int _m)
    {
        n = _n;
        m = _m;
        for(int i = 0;i <= m;i++)
        {
            S[i] = 0;
            U[i] = D[i] = i;
            L[i] = i-1;
            R[i] = i+1;
        }
        R[m] = 0; L[0] = m;
        SIZE = m;
        for(int i = 1;i <= n;i++)
            H[i] = -1;
    }
    void Link(int r,int c)
    {
        ++S[Col[++SIZE]=c];
        Row[SIZE] = r;
        D[SIZE] = D[c];
        U[D[c]] = SIZE;
        U[SIZE] = c;
        D[c] = SIZE;
        if(H[r] < 0)H[r] = L[SIZE] = R[SIZE] = SIZE;
        else
        {
            R[SIZE] = R[H[r]];
            L[R[H[r]]] = SIZE;
            L[SIZE] = H[r];
            R[H[r]] = SIZE;
        }
    }
    void exact_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];
                --S[Col[j]];
            }
    }
    void repeat_remove(int c) {
    for(int i = D[c]; i != c; i = D[i])
        L[R[i]] = L[i], R[L[i]] = R[i];
    }
    void repeat_resume(int c) {
    for(int i = U[c]; i != c; i = U[i])
        L[R[i]] = R[L[i]] = i;
    }

    int f() { //估价函数。
    bool vv[MaxM];
    int ret = 0, c, i, j;
    for(c = R[0]; c != 0; c = R[c]) vv[c] = 1;
    for(c = R[0]; c != 0; c = R[c])
        if(vv[c]) {
            ++ret, vv[c] = 0;
            for(i = D[c]; i != c; i = D[i])
                for(j = R[i]; j != i; j = R[j])
                    vv[Col[j]] = 0;
        }
    return ret;
    }

    void repeat_dance(int d) {
    if(d + f() >= ansd) return; //估价函数剪枝,A*搜索
    if(R[0] == 0) {
        if(d < ansd) ansd = d;
        return;
    }
    int c = R[0], i, j;
    for(i = R[0]; i; i = R[i])
        if(S[i] < S[c]) c = i;
    for(i = D[c]; i != c; i = D[i]) {
        repeat_remove(i);
        for(j = R[i]; j != i; j = R[j]) repeat_remove(j);
        repeat_dance(d + 1);
        for(j = L[i]; j != i; j = L[j]) repeat_resume(j);
        repeat_resume(i);
    }
}
    void exact_resume(int c)
    {
        for(int i = U[c];i != c;i = U[i])
            for(int j = L[i];j != i;j = L[j])
                ++S[Col[U[D[j]]=D[U[j]]=j]];
        L[R[c]] = R[L[c]] = c;
    }
    //d为递归深度
    bool exact_Dance(int d)
    {
        if(R[0] == 0)
        {
            ansd = d;
            return true;
        }
        int c = R[0];
        for(int i = R[0];i != 0;i = R[i])
            if(S[i] < S[c])
                c = i;
        exact_Remove(c);
        for(int i = D[c];i != c;i = D[i])
        {
            ans[d] = Row[i];
            for(int j = R[i]; j != i;j = R[j]) exact_Remove(Col[j]);
            if(exact_Dance(d+1))return true;
            for(int j = L[i]; j != i;j = L[j]) exact_resume(Col[j]);
        }
        exact_resume(c);
        return false;
    }
};

/***********************************************************************************
                               模板结束
***********************************************************************************/

二、把一个数独问题转化为精确覆盖问题

   1.数独问题

           给定一个9x9的矩阵。将1~9填入当中。其中有些格子已经填好了,有些格子则需要你填进去。

     对于填好后的矩阵,需要满足3个条件:

    1.   每一个数字在每一只能出现1次
    2.   每一个数字在每一只能出现1次
    3.   每一个数字在每一个九宫区域内只能出现1次。

           例如:

输入格式:数独9*9矩阵,0表示该格未填写数字,1~9表示该格已经填写有该数字。

样例输入:

4 0 0 0 7 0 1 0 0

0 0 1 9 0 4 6 0 5

0 0 0 0 0 1 0 0 0

0 0 0 7 0 0 0 0 2

0 0 2 0 3 0 0 0 0

8 4 7 0 0 6 0 0 0

0 1 4 0 0 0 8 0 6

0 2 0 0 0 0 3 0 0

6 0 0 0 9 0 0 0 0

    2.如何把数独问题表示为精确覆盖问题

           即 把数独矩阵转化为01覆盖矩阵。

对于精确覆盖问题的01矩阵,其行与列的意义:

      •   列:一个原问题的约束条件
      •   行:一个方案所满足的约束条件

精确覆盖的结果 选取若干个方案,每个方案可以满足一个或多个约束条件,每个条件恰被其中一个方案满足。

考虑数独问题的约束条件和方案。

约束条件:

1.每一个数字在每一行出现。

由于行和数字的互相匹配,因此一共会产生9x9,81个约束条件 。

1. 第1行存在数字1

2. 第1行存在数字2

3. 第1行存在数字3
...

9. 第1行存在数字9

10. 第2行存在数字1

11. 第2行存在数字2
...

18. 第2行存在数字9

19. 第3行存在数字1
...

80. 第9行存在数字8

81. 第9行存在数字9

对于第 i 行第 j 列填写数字 k 时,其对应的列序号为 (i-1)*9+k

2.每一个数字在每一列出现。

由于列和数字的互相匹配,因此一共会产生9x9,81个约束条件:

第 i 列存在数字 j。 (i=1~9, j=1~9)

对于第 i 行第 j 列填写数字 k 时,其对应的列序号为 81+(j-1)*9+k

3.每一个数字在每一个九宫出现。

         由于九宫和数字的互相匹配,因此一共会产生9x9,81个约束条件:

第 i 个九宫格存在数字 j。 (i=1~9, j=1~9)

对于第 i 行第 j 列填写数字 k 时,位于第t个九宫,其对应的约束条件序号为 162+(t-1)*9+k,

        其中 t = ((i - 1) / 3 * 3 + (j - 1) / 3) + 1;

4.每一个格子填了一个数字

由于格子和数字的互相匹配,因此一共会产生9x9,81个约束条件:

格子(i,j)填了数字。

对于 第 i 行第 j 列填写数字 k 时,其对应的约束条件序号为243+(i-1)*9+j

     

    合计9*9*4=324个约束条件,对应的01矩阵有324列。

  

     方案

每一个格子可能填1-9这 9 个数字,一共有81个格子,总共是729个方案。

方案 “第 i 行第 j 列填写数字 k” 对应的行序号(方案编号)(((i-1)*9+j)-1)*9+k

   合计9*9*9=729个方案,对应的01矩阵有729行。

     

   由此,9*9的数独问题转化为729*324的01矩阵的精确覆盖的问题。

   转化成01矩阵的代码:

 char s[10][10]; //原数独矩阵

void setmx(int i,int  j,int k){   //方案“第i行第j列填写数字k”

    int id = (i - 1) * 9 + j ;// 表示第i行第j列格子的编号
    int pid = (id - 1) * 9 + k; // 表示该格子填写k所对应的方案编号

    // 约束条件1 - 对应第1~81列
    // 第(i-1)*9+k列表示第i行存在数字k
    dlx.Link(pid,(i - 1) * 9 + k); //01矩阵(pid,(i - 1) * 9 + k)置1

    // 约束条件2 - 对应第82~162列
    // 第81+(j-1)*9+k列表示第j列存在数字k
dlx.Link(pid,81 + (j - 1) * 9 + k); //01矩阵(pid,81 + (j - 1) * 9 + k)置1

    // 约束条件3 - 对应第163~243列
    // 第162+(t-1)*9+k列表示第t个九宫存在数字k
    int t = ((i - 1) / 3 * 3 + (j - 1) / 3) + 1;
dlx.Link(pid,162 + (t - 1) * 9 + k);  //01矩阵(pid,162 + (t - 1) * 9 + k)置1

    // 约束条件4 - 对应第244~324列
    // 第243+id列表示第i行第j列填写有数字
    dlx.Link(pid,243 + id); //01矩阵(pid,243 + id)置1
}

void set_board()  //把原数独转化成729*324的01矩阵
{
    int i,j,k;
        for(i = 1; i <= 9; i++){
            for(j = 1; j <= 9; j++){
                if(s[i][j] == ‘0‘){ //位置(i,j)值不确定,可以为1~9
                    for(k = 1; k <= 9; k++){ //枚举可能的数字
                          setmx(i,j,k);
                    }
                }
                else{ //位置(i,j)值确定为k
                    k = s[i][j] - ‘0‘;
                    setmx(i,j,k);
                }
            }
        }
}

     3.由精确覆盖问题答案得到原数独问题的答案

      精确覆盖的结果 选取若干个方案,每个方案可以满足一个或多个约束条件,每个条件恰被其中一个方案满足

对应数独矩阵的意义

每个条件恰被其中一个方案满足

约束条件1 “第1行存在数字1” 恰被一个方案满足,即“第一行存在且仅存在一个数字1”。

                约束条件2 “第1行存在数字2” 恰被一个方案满足,即“第一行存在且仅存在一个数字2”。

……

                约束条件324 “格子(9,9)填了数字” 恰被一个方案满足,即“格子(9,9)填了并仅填了一个数字”

        729*324的01矩阵的精确覆盖 满足了9*9的数独填完的所有约束条件

选取若干个方案

方案一   “第 i1 行第 j1 列填写数字 k1”

                  方案二   “第 i2 行第 j2 列填写数字 k2”

                    ……

   选取的所有方案就是数独的答案。

对于每个方案, 第 i 行第 j 列填写数字 k

全部方案填入数独矩阵即为数独的答案。

  精确覆盖答案还原成数独答案:

选取的方案即 01矩阵选择的行,利用DangcingLinks求解。

数组ans[]记录 01矩阵选取的行序号,ansd表示选择的行总数。

行序号 pid = (((i - 1) * 9 + j)-1)+k,得到行序号 pid 即可还原(i,j, k)的值,即得到方案 “第 i 行第 j 列填写数字 k”。

#1321  AC代码:

#include <algorithm>
#include <cstring>
#include <string.h>
#include <iostream>
#include <list>
#include <map>
#include <set>
#include <stack>
#include <string>
#include <utility>
#include <queue>
#include <vector>
#include <cstdio>
#include <cmath>

#define LL long long

using namespace std;

/************************************************************************************
                      DLX模板 精确覆盖(exact_) 重复覆盖(repeat_)
************************************************************************************/

const int maxnode = 2361960+105;
const int MaxM = 324+10;  //01矩阵列数
const int MaxN = 729+10;  //01矩阵行数

struct DLX
{
    int n,m,SIZE;
    int U[maxnode],D[maxnode],R[maxnode],L[maxnode],Row[maxnode],Col[maxnode];//L,R,D,U四个数组记录某节点上下左右邻居
    int H[MaxN], S[MaxM];//H记录排头,S记录某列有多少个节点
    int ansd, ans[MaxN];
    void init(int _n,int _m)
    {
        n = _n;
        m = _m;
        for(int i = 0;i <= m;i++)
        {
            S[i] = 0;
            U[i] = D[i] = i;
            L[i] = i-1;
            R[i] = i+1;
        }
        R[m] = 0; L[0] = m;
        SIZE = m;
        for(int i = 1;i <= n;i++)
            H[i] = -1;
    }
    void Link(int r,int c)
    {
        ++S[Col[++SIZE]=c];
        Row[SIZE] = r;
        D[SIZE] = D[c];
        U[D[c]] = SIZE;
        U[SIZE] = c;
        D[c] = SIZE;
        if(H[r] < 0)H[r] = L[SIZE] = R[SIZE] = SIZE;
        else
        {
            R[SIZE] = R[H[r]];
            L[R[H[r]]] = SIZE;
            L[SIZE] = H[r];
            R[H[r]] = SIZE;
        }
    }
    void exact_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];
                --S[Col[j]];
            }
    }
    void repeat_remove(int c) {
    for(int i = D[c]; i != c; i = D[i])
        L[R[i]] = L[i], R[L[i]] = R[i];
    }
    void repeat_resume(int c) {
    for(int i = U[c]; i != c; i = U[i])
        L[R[i]] = R[L[i]] = i;
    }

    int f() { //估价函数。
    bool vv[MaxM];
    int ret = 0, c, i, j;
    for(c = R[0]; c != 0; c = R[c]) vv[c] = 1;
    for(c = R[0]; c != 0; c = R[c])
        if(vv[c]) {
            ++ret, vv[c] = 0;
            for(i = D[c]; i != c; i = D[i])
                for(j = R[i]; j != i; j = R[j])
                    vv[Col[j]] = 0;
        }
    return ret;
    }

    void repeat_dance(int d) {
    if(d + f() >= ansd) return; //估价函数剪枝,A*搜索
    if(R[0] == 0) {
        if(d < ansd) ansd = d;
        return;
    }
    int c = R[0], i, j;
    for(i = R[0]; i; i = R[i])
        if(S[i] < S[c]) c = i;
    for(i = D[c]; i != c; i = D[i]) {
        repeat_remove(i);
        for(j = R[i]; j != i; j = R[j]) repeat_remove(j);
        repeat_dance(d + 1);
        for(j = L[i]; j != i; j = L[j]) repeat_resume(j);
        repeat_resume(i);
    }
}
    void exact_resume(int c)
    {
        for(int i = U[c];i != c;i = U[i])
            for(int j = L[i];j != i;j = L[j])
                ++S[Col[U[D[j]]=D[U[j]]=j]];
        L[R[c]] = R[L[c]] = c;
    }
    //d为递归深度
    bool exact_Dance(int d)
    {
        if(R[0] == 0)
        {
            ansd = d;
            return true;
        }
        int c = R[0];
        for(int i = R[0];i != 0;i = R[i])
            if(S[i] < S[c])
                c = i;
        exact_Remove(c);
        for(int i = D[c];i != c;i = D[i])
        {
            ans[d] = Row[i];
            for(int j = R[i]; j != i;j = R[j]) exact_Remove(Col[j]);
            if(exact_Dance(d+1))return true;
            for(int j = L[i]; j != i;j = L[j]) exact_resume(Col[j]);
        }
        exact_resume(c);
        return false;
    }
};

/***********************************************************************************
                      模板结束
**********************************************************************************/

DLX dlx;
 char s[12][12]; //原数独矩阵

void setmx(int i,int  j,int k){
    int id = (i - 1) * 9 + j ;// 表示第i行第j列格子的编号
    int pid = (id - 1) * 9 + k; // 表示该格子填写k所对应的方案编号
    // 约束条件1 - 对应第1~81列
    // 第(i-1)*9+k列表示第i行存在数字k
    dlx.Link(pid,(i - 1) * 9 + k);

    // 约束条件2 - 对应第82~162列
    // 第81+(j-1)*9+k列表示第j列存在数字k
        dlx.Link(pid,81 + (j - 1) * 9 + k);

    // 约束条件3 - 对应第163~243列
    // 第162+(t-1)*9+k列表示第t个九宫存在数字k
    int t = ((i - 1) / 3 * 3 + (j - 1) / 3) + 1;
        dlx.Link(pid,162 + (t - 1) * 9 + k);

    // 约束条件4 - 对应第244~324列
    // 第243+id列表示第i行第j列填写有数字
    dlx.Link(pid,243 + id);
}

void set_board() //把原数独矩阵转化成729*324的01矩阵
{
    int i,j,k;
        for(i = 1; i <= 9; i++){
            for(j = 1; j <= 9; j++){
                if(s[i][j] == ‘0‘){ //位置(i,j)值不确定,可以为1~9
                    for(k = 1; k <= 9; k++){
                          setmx(i,j,k);
                    }
                }
                else{ //位置(i,j)值确定为k
                    k = s[i][j] - ‘0‘;
                    setmx(i,j,k);
                }
            }
        }
}

int ans[12][12]; //数独答案
void print()  //精确覆盖答案还原成数独答案
{
    int i,j,k;
    for(int id=0;id<dlx.ansd;id++)
        {
            int a=dlx.ans[id];  //ans[]存的是精确覆盖行号
            //行号 pid = (((i - 1) * 9 + j)-1)+k;
            //由此可以还原i,j,k的值
            //即得到信息,第i行第j列位置值为k
            //注意i,j,k取值范围均为1~9
            k=a%9; if(k==0) k=9; //得到k
            a-=k; a/=9; a+=1;
            j=a%9; if(j==0) j=9; //得到 j
            a-=j; a/=9; a+=1;
            i=a%9; if(i==0) i=9; //得到 i
            ans[i][j]=k;  //方案 第 i 行第 j 列填写数字 k
        }
 //输出数独答案
    for(i=1;i<=9;i++)
    for(j=1;j<=9;j++)
        {
            if(j!=9) cout<<ans[i][j]<<‘ ‘;
    else cout<<ans[i][j]<<endl;
        }
}

int main()
{
    int T;
    int n,m;
    scanf("%d",&T);
    while(T--)
    {
        for(int i = 1; i <= 9; i++)
            for(int j = 1; j <= 9; j++)
            scanf(" %c",&s[i][j]);      //输入原数独矩阵

          //9*9的数独问题转化为729*324的01矩阵的精确覆盖
        n = 729;                         //729个方案。对应01矩阵有729行。
        m = 324;                        //324个约束条件,对应的01矩阵有324列
        dlx.init(n,m);
        set_board();                    //数独矩阵转化为729*324的01矩阵
        dlx.exact_Dance(0);         //DLX求解精确覆盖
        print();                            //输出数独答案
    }
    return 0;
}
时间: 2024-10-11 06:19:12

hihoCoder #1321 : 搜索五?数独 (Dancing Links ,精确覆盖)的相关文章

ZOJ 3209 Treasure Map (Dancing Links 精确覆盖 )

题意 :  给你一个大小为 n * m 的矩形 , 坐标是( 0 , 0 ) ~ ( n , m )  .然后给你 p 个小矩形 , 坐标是( x1 , y1 ) ~ ( x2 , y2 ) , 你选择最小的几个矩形 , 使得这些矩形可以覆盖整个矩形 , 并且互相不会重叠 .( n , m <= 30 ) 思路 : Dancing Links 的精确覆盖问题 . 我们将 n * m 的矩形分成 n * m 个小正方形 ,那么我们只要保证每个小正方形被覆盖且只被覆盖一次即可 . 那么列表示每个小正

hust 1017 dancing links 精确覆盖模板题

最基础的dancing links的精确覆盖题目 1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <algorithm> 5 6 using namespace std; 7 #define N 1005 8 #define MAXN 1000100 9 10 struct DLX{ 11 int n , m , size;//size表示当前dlx表中有多少

[ACM] ZOJ 3209 Treasure Map ( Dancing Links 精确覆盖,矩形覆盖)

Treasure Map Time Limit: 2 Seconds      Memory Limit: 32768 KB Your boss once had got many copies of a treasure map. Unfortunately, all the copies are now broken to many rectangular pieces, and what make it worse, he has lost some of the pieces. Luck

HDU5046 Airport dancing links 重复覆盖+二分

这一道题和HDU2295是一样 是一个dancing links重复覆盖解决最小支配集的问题 在给定长度下求一个最小支配集,只要小于k就行 然后就是二分答案,每次求最小支配集 只不过HDU2295是浮点,这里是整数 我写的一个比较暴力 #include<cstdio> #include<cstring> #include<queue> #include<cstdlib> #include<algorithm> #include<vector

hdu 1426 Sudoku Killer ( Dancing Link 精确覆盖 )

利用 Dancing Link 来解数独 具体的可以看    lrj 的训练指南 和 < Dancing Links 在搜索中的应用 >这篇论文 Dancing Link 来求解数独 , 是通过求解精确覆盖 精确覆盖就是给出一个 01 矩阵 , 要求我们选择一些行 , 使得每一列有且仅有一个 1 对于数独问题 , 行就是我们的选择 , 即在第 i 行 第 j 列 放上 数字 k , 所以我们最多有 i * j * k 中选择 如果某些位置( x , y  )已经放了数字 a , 那么我们的选择

HDU 3335 Divisibility dancing links 重复覆盖

分析: dlx重复覆盖的巧用,重复覆盖的原理恰好符合本题的筛选方式,即选择一个数后,该数的倍数或约数可以保证在之后的搜索中不会被选择 于是修改一下启发函数,求解最大的重复覆盖即可. 其实不一定不被选择,只是选择以后,要么达不成目标,要不达到目标,也不如不选择更优 举下面的例子 3 2 3 6 答案一看就是 2 初始的dancing links的表示是这样的 2   3   6 2    1   0   1 3    0   1   1 6    1   1   1 然后肯定先选第一列进行删 删

HDU 2295 Radar( 二分+Dancing Links重复覆盖 )

题意 : 有 n 个城市 , m个站 , 你要选择 k 个站 , 每个站画一个半径为 r 的圆 , 可以覆盖所有的城市 , 一个城市可以被多个站覆盖 .求的是满足要求的最小的 r . 思路很明显了 , 首先是二分 , 将问题转化成可行性判定的问题 . 那么对于 mid , 我们将 站看成行 , 将城市看成 列 , 如果一个站和一个城市的距离小于mid , 那么对应的矩阵位置的值就1 , 否则是0 , 用dlx 重复覆盖来解决 重复覆盖和精确覆盖主要有两个区别 : 第一, remove 和 res

HDU 5046 Airport ( Dancing Links 重复覆盖 )

今年上海网络赛的一道题目 , 跟 HDU 2295 如出一辙 , 就是距离的计算一个是欧几里得距离 , 一个是曼哈顿距离 学完DLX感觉这题好水 ,就是一个裸的重复覆盖 注意下别溢出就行了 #include <stdio.h> #include <string.h> #include <algorithm> #include <vector> #include <math.h> #include <stdlib.h> using na

HDU 5046 Airport ( Dancing Links 反复覆盖 )

今年上海网络赛的一道题目 , 跟 HDU 2295 如出一辙 . 就是距离的计算一个是欧几里得距离 , 一个是曼哈顿距离 学完DLX感觉这题好水 ,就是一个裸的反复覆盖 注意下别溢出即可了 #include <stdio.h> #include <string.h> #include <algorithm> #include <vector> #include <math.h> #include <stdlib.h> using na