《算法竞赛进阶指南》数独

数独是一种传统益智游戏,你需要把一个 9×99×9 的数独补充完整,使得数独中每行、每列、每个 3×33×3 的九宫格内数字 1∼91∼9 均恰好出现一次。

请编写一个程序填写数独。

输入格式

输入包含多组测试用例。

每个测试用例占一行,包含 8181 个字符,代表数独的 8181 个格内数据(顺序总体由上到下,同行由左到右)。

每个字符都是一个数字(1−91−9)或一个 .(表示尚未填充)。

您可以假设输入中的每个谜题都只有一个解决方案。

文件结尾处为包含单词 end 的单行,表示输入结束。

输出格式

每个测试用例,输出一行数据,代表填充完全后的数独。

输入样例:

4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end

输出样例:

417369825632158947958724316825437169791586432346912758289643571573291684164875293
416837529982465371735129468571298643293746185864351297647913852359682714128574936

解题思路:

1:选择一个每一行每一列每个九宫格内所能选择数最少的数(确保分支最小)
2:判断改方案是否合理,即同行/列/九宫格是否存在重复数
3:利用二进制的表示法:1表示该位置能填数,0表示该位置不能填数:
例如:1111 1111 0表示[2, 9]均可以填,而1不能填 

#include <cstdio>
#include <iostream>

using namespace std;

const int N = 9, M  = 1 << N;

char str[100];
int ones[M], map[M];//ones是计算一个正数表示为二进制数种1的数目,
                    //map为将2的n次方转化为n:map[2的n次方] = n;
int row[N], col[N], cell[3][3];//row, col, cell分别表示每行,每列,每个九宫格;

void init()//将每行,每列,每个九宫格都填上1,即为每个格子都能填数
{
    for (int i = 0; i < N; i ++ ) row[i] = (1 << N) - 1;
    for (int j = 0; j < N; j ++ ) col[j] = (1 << N) - 1;
    
    for (int i = 0; i < 3; i ++ )
        for (int j = 0; j < 3; j ++ )
            cell[i][j] = (1 << N) - 1;
}

void draw(int x, int y, int t, bool is_set)//在x, y位置上填1 << t;若is_set = true表示填数,
{                                         //反之删除一个数
    if (is_set) str[x * N + y] = t + '1';
    else str[x * N + y] = '.';
    
    int v = 1 << t;
    if (!is_set) v = -v;
    
    row[x] -= v;//改变第x行的数
    col[y] -= v;//改变第y行的数
    cell[x / 3][y / 3] -= v;//改变九宫格的数
}

int get(int x, int y)//返回第x行,第y列 ,此(x, y)所对应的九宫格中没有重复的数
{
    return row[x] & col[y] & cell[x / 3][y / 3];
}

int lowbit(int x)//返回二进制中的最后一个1, 答案以2的次方表示例如:
{                //1000返回的是2的3次幂
    return x & - x;
}

bool dfs(int cnt)
{
    if (!cnt) return true;//说明以及搜完
    
    int x, y;
    int minv = 10;//用来记录从哪个坐标开始枚举的选择最少
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j < N; j ++ )
            if (str[i * N + j] == '.')//若该位置可以填数
            {
                int state = get(i, j);//找到该位置能填数的状态
                if (ones[state] < minv)//若该位置的最小选择可以被更新
                {
                    x = i;
                    y = j;
                    minv = ones[state];
                }
            }
    
    int state = get(x, y);//说明x, y即为最小选择数目的方案,state即为最小选择数目的状态
    for (int i = state; i ; i -= lowbit(i))//每次除去末尾最后一个1
    {
        int t = map[lowbit(i)];//lowbit返回的是2的次幂,用map找出他的次幂
        draw(x, y, t, true);//在第x行,第y列,以及它所对应的九宫格上加上1 << t;
        if (dfs(cnt - 1)) return true;
        draw(x, y, t, false);//恢复现场
    }
    
    return false;
}

int main()
{
    for (int i = 0; i < N; i ++ ) map[1 << i] = i;//预处理每个2的n次方所对应的次幂
    
    for (int i = 0; i < M; i ++ )//预处理每个i转化为二进制有多少个1;
        for (int j = 0; j < N; j ++ )
            ones[i] += i >> j & 1;
    
    while (cin >> str, str[0] != 'e')
    {
        init();//初始化九宫格
        
        int cnt = 0;
        for (int i = 0, k = 0; i < N; i ++ )
            for (int j = 0; j < N; j ++, k ++ )
                if (str[k] != '.')//说明能填数
                {
                    int t = str[k] - '1';
                    draw(i, j, t, true);//将(i, j)坐标上填上1 << t这个数
                }else cnt ++ ;//若不能填数,则所需要填的数++
        
        dfs(cnt);
        
        puts(str);
    }
    
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啥也不会hh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值