166. 数独 DFS 之 剪枝 二进制 位运算 优化 思维

35 篇文章 1 订阅
20 篇文章 1 订阅

题目

在这里插入图片描述

题解思路

4大剪枝常用剪枝策略
在这里插入图片描述
首先进行优化搜索顺序,每次搜索可以放的数最少的的点 。

再根据行列九宫格不能相同,想到位运算优化(我是想不到)。

利用按位与 & 的 清零性质 (即一位为0 按位与出来的一定是 0 )
用 0 表示放了这个位置的数 , 1表示没放 。

这样的话 每行列和九宫格的 所有状态 就可以用一个 2 ^ 9 - 1 的 数 表示了

其次这里可以lowbit来快速找出1的位置来让我们放数 。

在这里插入图片描述

实现

初始化

将 所有行列 子棋盘的状态变为1 即表示都没放 即 2 ^ 9 - 1

我们先将 棋盘中已经有的数放入棋盘 , 将 这个数的行列以及归属的子棋盘 的状态更新 。 即 减上这个数的二进制 表示 。 ( 如 2 (10) 4(1000)) 代表这个位置放了数 ( 0 代表放了)。

这里可以写个函数 因为之后我们会再放数进去 。

再进去 dfs 去搜索出能放的答案

每次搜索可以放的数最少的的点

这样我们要对整个棋盘搜索出每个可以放的位置 可以放的数量 (即状态为1 的总数 ) 取 min
这里我们只有 对这个位置的3个状态 & 可以得出他的总状态 ,但是lowbit只能得出最低位的1 , 这样的话 我们要预处理出一个代表每个状态有多少个1的数组 nosum

然后取出这个1最小的状态 , 利用lowbit 拿出 每个 1 进行尝试放入数 。
这样又有问题了,我们只有这个状态值的1,但是我们要的是直接得出这个位置放什么数,
又是预处理。。。,我们利用一个数组mp存入每个1的真正代表的数 即 100 代表3 1000 代表 4
但是位运算左移缺了1 1 >> 1 得出的是 10 1>>2 得出 100 这样我们就在放入棋盘的函数中修改让他把数自增一个1即直接加 '1 ’

这样我们只需在尝试放入后,进行复原(回溯) 。
但是我们要保证答案在原地要怎么呢 ?

复原前需要知道这个点是不是答案。这样我们只需要在搜索到答案的时候return true , 当dfs返回true时就不需要复原了。

这题磕了很久,没有找到写的仔细的博客,自己写了个。

AC代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
#include <map>
#include <string>
using namespace std;

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

int nosum [M] , mp[M] ;
int row[N] , col [N] , cell[3][3] ;
char st[100] ;


void init()
{
     for (int i = 0 ; i < N ; i++ )
        row[i] = col[i] = (1 << N ) - 1 ;

     for (int i = 0 ; i < 3 ; i++ )
        for (int j = 0 ; j < 3 ; j++ )
            cell[i][j] = (1 << N) - 1 ;
}

int lowbit (int x )
{
    return x & -x ;
}

void draw ( int x ,int y , int t , bool is_set )
{
    if ( is_set )
        st [ x * N + y ] = '1' + t ;
    else
        st [ x*N + y ] = '.' ;

    int v = 1 << t ;

    if ( ! is_set )
        v = - v ;

    row[x] -= v ;
    col[y] -= v ;
    cell[ x / 3 ][ y / 3 ] -= v ;

}

int gett( int x , int y )
{
    return cell[x/3][y/3] & row[x] & col[y] ;
}

bool  dfs( int p )
{
    if ( p == 0 )
        return true ;
    int mi = N ;
    int x , y ;
    for (int i = 0 ; i < N ; i++ )
        for (int j = 0 ; j < N ; j++ )
        {
            if ( st [ i*N + j ] == '.' )
            {
                int sat = gett( i , j ) ;
                if ( nosum[sat] < mi )
                {
                    mi = nosum[sat] ;
                    x = i ;
                    y = j ;
                }
            }
        }

    int sat = gett( x , y ) ;
    for (int i = sat ; i ; i -= lowbit(i) )
    {
        int t = mp[lowbit(i)] ;
        draw( x , y , t , 1 ) ;
        if ( dfs( p - 1 ) )
            return true ;
        draw( x , y , t , 0 ) ;
    }
    return false ;
}


int main ()
{
    ios::sync_with_stdio(false);

    for (int i = 0; i < N; i ++ )
        mp[1 << i] = i;

    for (int i = 0 ; i < 1 << N ; i++ )
        for (int j = 0 ; j < N ; j++ )
            nosum[ i ] += i >> j & 1 ;

    while ( cin >> st , st[0] != 'e' )
    {
        init() ;

        int cnt = 0 ;

        for (int i = 0 , k = 0 ; i < N ; i++ )
            for (int j = 0 ; j < N ; j++ , k++ )
            {
                if ( st[k] != '.' )
                {
                    int t = st[k] - '1' ;
                    draw( i , j , t  , 1 ) ;
                }else
                    cnt++ ;

            }

        dfs(cnt) ;

        cout << st << "\n" ;
    }

    return 0 ;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值