数独

数独游戏,一个比较经典的搜索剪枝优化题,POJ中有2676,2918,3074,3076四道数独问题。其难度大致是2676=2918<3074<3076。下面这道用暴搜加上一个小剪枝就可以过。

https://cn.vjudge.net/contest/245662#problem/E

数独游戏的规则是这样的:在一个9x9的方格中,你需要把数字1-9填写到空格当中,并且使方格的每一行和每一列中都包含1-9这九个数字。同时还要保证,空格中用粗线划分成9个3x3的方格也同时包含1-9这九个数字。比如有这样一个题,大家可以仔细观察一下,在这里面每行、每列,以及每个3x3的方格都包含1-9这九个数字。

例题:


答案:

 

Input

本题包含多组测试,每组之间由一个空行隔开。每组测试会给你一个 9*9 的矩阵,同一行相邻的两个元素用一个空格分开。其中1-9代表该位置的已经填好的数,问号(?)表示需要你填的数。

Output

对于每组测试,请输出它的解,同一行相邻的两个数用一个空格分开。两组解之间要一个空行。
对于每组测试数据保证它有且只有一个解。

Sample Input

7 1 2 ? 6 ? 3 5 8
? 6 5 2 ? 7 1 ? 4
? ? 8 5 1 3 6 7 2
9 2 4 ? 5 6 ? 3 7
5 ? 6 ? ? ? 2 4 1
1 ? 3 7 2 ? 9 ? 5
? ? 1 9 7 5 4 8 6
6 ? 7 8 3 ? 5 1 9
8 5 9 ? 4 ? ? 2 3

Sample Output

7 1 2 4 6 9 3 5 8
3 6 5 2 8 7 1 9 4
4 9 8 5 1 3 6 7 2
9 2 4 1 5 6 8 3 7
5 7 6 3 9 8 2 4 1
1 8 3 7 2 4 9 6 5
2 3 1 9 7 5 4 8 6
6 4 7 8 3 2 5 1 9
8 5 9 6 4 1 7 2 3

思路:用a,b,c数组分别计录行,列 和 小矩阵中是否已经填入了某个数字,再用数组xx[]和yy[]记录空白位置的坐标。dfs的时候搜索这些坐标,依次查看从1到9的数字是否可以填入,如果可以填入则填入。一个数字如果可以填入某个位置,那它一定没有出现在这一行、这一列和这个小矩阵中。

#include<iostream>
using namespace std;

int v[20][20],xx[100],yy[100],num;
bool f,gg,a[20][20],b[20][20],c[20][20];    //a[i][x]记录第i行有没有用过x,b数组记录列,c数组记录小方块

void shuru(char s,int x,int y){
    int q;
    if (s=='?'){
        num++;
        xx[num]=x;
        yy[num]=y;
        v[x][y]=0;
    }
    else{
        q=s-'0';
        v[x][y]=q;
        a[x][q]=1;
        b[y][q]=1;
        c[((x-1)/3)*3+((y-1)/3)+1][q]=1;    //通过计算,用x和y表示出当前位置在第几个小方块。
    }
}

void sousuo(int z)    //z表示正在第几个空白位置。
{
    int i,j,x,y,d;
    if (z>num) {
        gg=1;
        return;    
    }
    for (i=1;i<=9;i++)
    {
        x=xx[z];
        y=yy[z];
        d=((x-1)/3)*3+((y-1)/3)+1;
        if (!a[x][i]&&!b[y][i]&&!c[d][i]){
            a[x][i]=1;
            b[y][i]=1;
            c[d][i]=1;
            v[x][y]=i;
            sousuo(z+1);
            if (gg) return;
            a[x][i]=0;
            b[y][i]=0;
            c[d][i]=0;
        }
    }
}

int main(){
    int i,j;
    char s;
    f=0;
    while (cin>>s)
    {
        for (i=1;i<=9;i++)
            for (j=1;j<=9;j++){
                a[i][j]=0;
                b[i][j]=0;
                c[i][j]=0;
            }
        num=0;
        shuru(s,1,1);
        for (i=2;i<=9;i++) {
            cin>>s;
            shuru(s,1,i);
        }
        for (i=2;i<=9;i++)
            for (j=1;j<=9;j++) {
                cin>>s;
                shuru(s,i,j);
            }
        gg=0;
        sousuo(1);
        if (f) cout<<endl;    
        for (i=1;i<=9;i++)
            for (j=1;j<=9;j++){
                cout<<v[i][j];
                if (j==9) cout<<endl;
                else cout<<' ';
            }
        f=1;
    }
    
    return 0;
}

下面的3074用刚才的方法就过不了了,需要加入其他的剪枝优化。首先,对于某个格子,如果某行或某列或某个小矩阵可以填入数字k的位置只有一个,那么就把数字k填入这个位置。然后,对于每一个空白位置统计它可以填入的数字个数,搜索的时候从可以填入的数字比较少的位置开始搜索。

快速失败的好处是我们避免了尝试后面的所有可能性。

在具体实现的时候,我们另开一个数组,来记录某一行或某一列或某一小矩阵的能填入数字k的位置个数。然后在搜索的时候,每次填入一个新的数字之后就扫描一遍所有的行,列和小矩阵,如果能填入数字k的位置只有一个,我们就将数字k填入这个位置。但是我们只记录了能填入数字k的位置个数,而没有记录它的位置,那么如何找到这个位置呢?我们枚举所有的空白位置,如果它是在这一个行/列/小矩阵,且可以填入这个数字,那么我们就将它填入数字k。这样,我们就将所有只能填入一个数字的位置都填好了。

那么现在有个问题,就是如何回溯。我们把所有能填入数字k的位置只有一个的情况都填上了,回溯的时候怎么把它撤回呢?我们应用一个小技巧,就是不真的把这个位置填上k,而是用bool型数组s记录下行/列/小矩阵能不能填数字k。如果是上述那种情况,我们就只把k赋值为真,其余都为假。然后这个位置能填的数字个数为1。(p[i]=1)

这样,在dfs的时候每次取p[i]最小的位置,再枚举数字k,如果s[i][k]为真就填入。

//s[i][j]记录的是第i个空白位置可不可以填数字j

 

#include <cstdio>
#include <string.h>

int v[20][20],xx[100],yy[100],p[100],num;
bool f,gg,a[20][20],b[20][20],c[20][20],s[100][10];    //a[i][x]记录第i行有没有用过x,b数组记录列,c数组记录小方块

void update()          //更新
{
    int oprow[10][10],opcol[10][10],opblock[10][10];  // 记录每行,列,小矩阵中数字k一共有几个可以放置的地方
    memset(oprow,0,sizeof(oprow));
    memset(opcol,0,sizeof(opcol));
    memset(opblock,0,sizeof(opblock));

    for (int i = 1; i <= num; i++)
    {
        int x=xx[i];
        int y=yy[i];
        if (v[x][y] == 0 ){
            p[i]=0;
            for (int j = 1; j <= 9 ; j++)
            {
                s[i][j]=0;
                if (a[x][j] == 0 && b[y][j]==0 && c[(x-1)/3*3+(y-1)/3+1][j]==0){
                    p[i]++;         //记录这个位置可以填入的数字个数
                    s[i][j]=1;
                    oprow[x][j]++;
                    opcol[y][j]++;
                    opblock[(x-1)/3*3+(y-1)/3+1][j]++;
                }
            }
        }
    }

    for (int i = 1; i <= 9 ; i++)      //第i行
        for (int j = 1; j <= 9; j++)      //填入数字j
        {
            if (oprow[i][j]==1)
               {
                    for (int k = 1; k <= num; k++)  //第k个空白位置
                    {
                        if (xx[k]==i && v[xx[k]][yy[k]]==0 && s[k][j]==1)
                        {
                            p[k]=1;
                            for (int u = 1; u <=9 ; u++)
                            {
                                s[k][u]=0;
                            }
                            s[k][j]=1;
                            break;
                        }
                    }
               }   
           if (opcol[i][j]==1)
           {
                for (int k = 1; k <= num; k++)
                 {
                    if (yy[k]==i && v[xx[k]][yy[k]]==0 && s[k][j]==1)
                    {
                        p[k]=1;
                        for (int u = 1; u <=9 ; u++)
                        {
                            s[k][u]=0;
                        }
                        s[k][j]=1;
                        break;
                    }
                }
            }   
            if (opblock[i][j]==1)
            {
                for (int k = 1; k <= num; k++)
                {
                    if ((xx[k]-1)/3*3+(yy[k]-1)/3+1==i && v[xx[k]][yy[k]]==0 && s[k][j]==1)
                    {
                        p[k]=1;
                        for (int u = 1; u <=9 ; u++)
                        {
                            s[k][u]=0;
                        }
                        s[k][j]=1;
                        break;
                    }
                }
            }   
        }
}

int getnow()           //找到当前可填入数字最少的位置
{
    int tmp=10, mark=-1, x, y;
    for (int i=1; i<=num; i++){
        x=xx[i];
        y=yy[i];
        if (tmp > p[i] && v[x][y] == 0){
            tmp = p[i];
            mark = i;
        }
    }
    return mark;
}


void shuru(char s,int x,int y){
    int q;
    if (s=='.'){
        num++;
        xx[num]=x;
        yy[num]=y;
        v[x][y]=0;
    }
    else{
        q=s-'0';
        v[x][y]=q;
        a[x][q]=1;
        b[y][q]=1;
        c[((x-1)/3)*3+((y-1)/3)+1][q]=1;    //通过计算,用x和y表示出当前位置在第几个小方块。
    }
}

void sousuo(int z)    //z表示正在第几个空白位置。
{
    int i,x,y,d;
    if (z>num) {
        gg=1;
        return;    
    }//printf("%d\n",z );
    int now=getnow();        //当前可填入数字最少的位置
    if (now==-1) return;
    x=xx[now];
    y=yy[now];
    for (i=1;i<=9;i++)
    if (s[now][i])
    {
        d=((x-1)/3)*3+((y-1)/3)+1;
        a[x][i]=1;
        b[y][i]=1;
        c[d][i]=1;
        v[x][y]=i;
        update();
        sousuo(z+1);
        if (gg) return;
        a[x][i]=0;
        b[y][i]=0;
        c[d][i]=0;
        v[x][y]=0;
        update();
    }
}

void init()
{
    int i,j;
    char s[100];
    f=0;

    while (gets(s))
    {
        if (s[0]=='e') break;
        num=0;
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        memset(c,0,sizeof(c));
        for (i=1;i<=9;i++)
            for (j=1;j<=9;j++) {
                int x=(i-1)*9+j-1;
                shuru(s[x],i,j);
            }
        gg=0;
        update();
        sousuo(1);
        if (f) printf("\n");    
        for (i=1;i<=9;i++)
            for (j=1;j<=9;j++){
                printf("%d",v[i][j]);
            }
        f=1;
    }

}

int main(){
    
    init();
    
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值