HDU 3111 Sudoku ( Dancing Links 精确覆盖模型 )


推荐两篇学DLX的博文:

http://bbs.9ria.com/thread-130295-1-1.html(这篇对DLX的工作过程演示的很详细)

http://yzmduncan.iteye.com/blog/1151695(这篇对精确覆盖与重复覆盖解释的简洁清晰,模板来自这篇博文)

以下转载:

DLX解决9*9的数独问题,转化为729*324的精确覆盖问题

行:一共9*9*9==729行。一共9*9小格,每一格有9种可能性(1-9),每一种可能都对应着一行。

列:一共(9+9+9)*9+81==324种前面三个9分别代表着9行9列和9小块,乘以9的意思是9种可能(1-9),因为每种可能只可以选择一个。81代表着81个小格,限制着每一个小格只放一个数字。

读入数据后,如果为'.',则建9行,即有1-9种可能,否则建一行,表示某小格只能放确定的某个数字。

以下个人理解:

列:一共(9+9+9)*9+81==324种

对于这个(9+9+9)*9 我是这么理解的:一个数a,它在某一行有9个位置可以放,在某一列有9个位置可以放,在某一个3×3小格中有9个位置可以放,所以一共可以放的位置是(9+9+9)个,然后一共9个数字,所以是(9+9+9)*9 。

不知道这样理解是否正确,望大家指教!

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>

using namespace std;

const int INF = 1 << 30;
const int SZ = 9;
const int MAXR = SZ * SZ * SZ;
const int MAXC = ( SZ+SZ+SZ )*SZ + SZ*SZ;

char mat[SZ+10][SZ+10];
char str[SZ+10];
bool maxtri[MAXR+40][MAXC+40];    //01矩阵
int C[(MAXR+40)*(MAXC+40)], cnt[MAXC+40];
int U[(MAXR+40)*(MAXC+40)], D[(MAXR+40)*(MAXC+40)];
int L[(MAXR+40)*(MAXC+40)], R[(MAXR+40)*(MAXC+40)];
int head;
int ans[MAXR+40];
int val[SZ+10][SZ+10];

void Remove( int c )
{
    int i, j;
    L[ R[c] ] = L[c];
    R[ L[c] ] = R[c];
    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];
            --cnt[ C[j] ];
        }
    }
    return;
}

void Resume( int c )
{
    int i, j;
    R[ L[c] ] = c;
    L[ R[c] ] = c;
    for ( i = D[c]; i != c; i = D[i] )
    {
        for ( j = R[i]; j != i; j = R[j] )
        {
            U[ D[j] ] = j;
            D[ U[j] ] = j;
            ++cnt[ C[j] ];
        }
    }
    return;
}

bool DFS( int cur )
{
    int i, j, c, minv;
    if ( R[head] == head )
        return true;

    minv = INF;
    for ( i = R[head]; i != head; i = R[i] )
    {
        if ( cnt[i] < minv )
        {
            minv = cnt[i];
            c = i;
        }
    }

    Remove(c);
    for ( i = D[c]; i != c; i = D[i] )
    {
        ans[cur] = (i - 1)/MAXC;
        for( j = R[i]; j != i; j = R[j] )
            Remove( C[j] );

        if ( DFS( cur + 1 ) ) return true;

        for( j = R[i]; j != i; j = R[j] )
            Resume( C[j] );
    }

    Resume(c);
    return false;
}

bool build()
{
    int i, j, cur, pre, first;
    head = 0;
    for ( i = 0; i < MAXC; ++i )
    {
        R[i] = i + 1;
        L[i + 1] = i;
    }
    R[ MAXC ] = 0;
    L[0] = MAXC;

    //列双向链表
    for ( j = 1; j <= MAXC; ++j )
    {
        pre = j;
        cnt[j] = 0;
        for ( i = 1; i <= MAXR; ++i )
        {
            if ( maxtri[i][j] )
            {
                ++cnt[j];
                cur = i * MAXC + j;  //当前节点的编号
                C[cur] = j;          //当前节点所在列
                D[pre] = cur;
                U[cur] = pre;
                pre = cur;
            }
        }
        U[j] = pre;
        D[pre] = j;
        if ( !cnt[j] ) return false; //一定无解
    }

    //行双向链表
    for ( i = 1; i <= MAXR; ++i )
    {
        pre = first = -1;
        for ( j = 1; j <= MAXC; ++j )
        {
            if( maxtri[i][j] )
            {
                cur = i * MAXC + j;
                if ( pre == -1 ) first = cur;
                else
                {
                    R[pre] = cur;
                    L[cur] = pre;
                }
                pre = cur;
            }
        }
        if ( first != -1 )
        {
            R[pre] = first;
            L[first] = pre;
        }
    }
    return true;
}

/**************以上DLX模板*****************/

void show()
{
    for ( int i = 0; i <= MAXR; ++i )
    {
        for ( int j = 0; j <= MAXC; ++j )
            printf( "%d", maxtri[i][j] );
        puts("");
    }
    return;
}

//得到该情况下的01矩阵
void init()
{
    memset( maxtri, false, sizeof(maxtri) );

    for ( int i = 1; i <= SZ; ++i )
    {
        for ( int j = 1; j <= SZ; ++j )
        {
            int col = ( i - 1 ) * SZ + j;  //格子编号(1-81)
            if ( mat[i][j] == '?' )
            {
                for ( int k = 1; k <= SZ; ++k )
                {
                    maxtri[ (col - 1)*SZ + k ][col] = true;      //81保证不重复
                    maxtri[ (col - 1)*SZ + k ][ 81 + (i - 1)*SZ + k ] = true;    //9行中哪一行
                    maxtri[ (col - 1)*SZ + k ][ 162 + (j - 1)*SZ + k ] = true;   //9列中哪一列
                    maxtri[ (col - 1)*SZ + k ][ 243 + ((i-1)/3*3 + (j-1)/3 )*SZ + k ] = true;//9小格中哪一个小格
                }
            }
            else
            {
                int k = mat[i][j] - '0';
                maxtri[ (col - 1)*SZ + k ][col] = true;
                maxtri[ (col - 1)*SZ + k ][ 81 + (i - 1)*SZ + k ] = true;
                maxtri[ (col - 1)*SZ + k ][ 162 + (j - 1)*SZ + k ] = true;
                maxtri[ (col - 1)*SZ + k ][ 243 + ((i-1)/3*3 + (j-1)/3 )*SZ + k ] = true;
            }
        }
    }
    //show();

    return;
}

void PrintAns()
{
    for ( int i = 0; i < 81; ++i )
    {
        int num = ans[i];
        int gird = num / SZ;
        if ( num % SZ ) ++gird;
        int aaa = num % SZ;
        if ( aaa == 0 ) aaa = 9;
        int x = (gird-1)/SZ+1;
        int y = (gird-1)%SZ+1;
        val[x][y] = aaa;
    }

    for ( int i = 1; i <= SZ; ++i )
    {
        for ( int j = 1; j <= SZ; ++j )
            printf( "%d", val[i][j] );
        puts("");
    }
    return;
}

int main()
{
    //freopen( "in.txt", "r", stdin );
    //freopen( "out.txt", "w", stdout );
    int T;
    scanf( "%d", &T );
    while ( T-- )
    {
        for ( int i = 1; i <= SZ; ++i )
            scanf( "%s", &mat[i][1] );
        if ( T ) scanf( "%s", str );
        init();
        if ( build() )
        {
            if ( DFS(0) )
                PrintAns();
            else puts("impossible");
        }
        else puts("impossible");
        if ( T ) puts("---");
    }
    return 0;
}

昨天比赛做到一个题,正解DLX,不过让薛薛位运算+剪枝直接暴过去了……跪。

为了保险起见,今天学了一下DLX。目前只明白了精确覆盖,对重复覆盖还不是很了解。

那个题似乎不是个很简单的DLX,需要精确覆盖+重复覆盖。orz,再接再厉吧。

不得不说,DLX的剪枝效果真是让人惊叹……

优质内容筛选与推荐>>
1、P5445 [APIO2019]路灯(树套树)
2、java学习总结
3、清华大学 pip 源
4、关于考试的几张试卷
5、线性模型的最小二乘法拟合(转)


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号