算法實踐:數獨(2)

數獨(2)

描述

在這裏插入圖片描述

輸入

輸入包含多組測試用例。

每個測試用例佔一行,包含81個字符,代表數獨的81個格內數據(順序總體由上到下,同行由左到右)。

每個字符都是一個數字(1-9)或一個”.”(表示尚未填充)。

您可以假設輸入中的每個謎題都只有一個解決方案。

文件結尾處爲包含單詞“end”的單行,表示輸入結束。

輸出

每個測試用例,輸出一行數據,代表填充完全後的數獨。

輸入樣例

.2738..1..1...6735.......293.5692.8...........6.1745.364.......9518...7..8..6534.
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end

輸出樣例

527389416819426735436751829375692184194538267268174593643217958951843672782965341
416837529982465371735129468571298643293746185864351297647913852359682714128574936

難度

極難,深搜

解法

從數獨中數字最多,空缺最少的位置開始深搜

用9位二進制數代表1-9行的一行,一列,一個3*3的格子,int初始化位111111111

可以使用lowbit計算整數中1的個數,即空缺的個數

也可以使用lowbit計算整數中最低爲的位數,即要修改的個數

代碼

//code by Andy
#include<bits/stdc++.h>
using namespace std;
const int N9 = 9; 
const int MaxN=1<<N9;  //! 1<<9爲512
string str;  
int row[N9],col[N9],cell[3][3];
int ones[MaxN];  //int t=ones[n] 給定一個數n,計算出有t個1
int LOG2[MaxN];  //int t= LOG2[n] 給定一個數n,計算出最低位的1
//! 二進制準備---------------------------------------------
//打表法計算LOG2
void BuildLOG2(){
    for(int i=0;i<N9;i++) LOG2[1<<i] = i;
}
//取n二進制序列最後末一個1000模式的子串
inline int lowbit(int n){
    return n & -n; //!-n表示n取反+1
}
//計算傳入的數有多少個是1
int NumberOf1(int n){
    int res = 0;
    while(n){
        n -=lowbit(n);
        res +=1;
    }
    return res;
}
//打表法構建ones數組,用於統計1最少的i就是需要搜索的位置
void Buildones(){
    for(int i=0;i<MaxN;i++) ones[i]=NumberOf1(i);
}
//! --------------------------------------------
//取交集的運算
inline int get(int x,int y){  //返回row[x],col[x]與cell[x][y]的可用集合
    return row[x] & col[y] & cell[x/3][y/3];  //返回同時爲1的二進制位
    // 可以使用lowbit取出每一位,就知道哪一位可嘗試了 111(7) & 011(3) = 011(3)
}
//把x,y位置二進制數的第n位取反
inline void flipbits(int x,int y,int n){
    row[x] ^= 1<< n;
    col[y] ^= 1<< n;
    cell[x/3][y/3] ^= 1<< n;
}
void init(){  //把全部數都清成1
    for(int i=0;i<N9;i++) row[i]=MaxN-1;  //初始化爲111111111
    for(int i=0;i<N9;i++) col[i]=MaxN-1;  //初始化爲111111111
    for(int i=0;i<3;i++)
        for(int j=0;j<3;j++)
            cell[i][j] = MaxN-1;  //初始化爲111111111
}
void printStr2line(string s){
    cout<<s<<endl;
}
//!========================================================================
bool dfs(int cellsLeft){
    if(cellsLeft==0){
        //輸出可行解
        printStr2line(str);
        return true;
    }
    //找出可選方案數最小的格子
    int minv = 10;
    int x,y;
    for(int i=0;i<N9;i++)
        for(int j=0;j<N9;j++)
            if(str[i*9+j]=='.'){
                int t = ones[get(i,j)];  //計算出格子最少的行列
                if(t<minv){
                    minv = t; x = i; y = j; //記錄爲x,y
                }
            }
    //從該方案[x,y,minV]開始枚舉,直到找到cellstosolve-1方案爲止
    //? get(x,y)的返回值代表行,列,Cell中可選的二進制值
    for(int sk=get(x,y);sk;sk-=lowbit(sk)){
        int t=LOG2[lowbit(sk)];  //得到1的最低位位置
        //修改狀態row,col,cell
        flipbits(x,y,t);
        str[x*9+y] = '1'+t;  //修改str對應位置上的數(0-8)映射到(1-9)
        dfs(cellsLeft-1);
        //恢復狀態
        str[x*9+y]='.';
        flipbits(x,y,t);
    }
    return false;
}
//! ========================================================================
int main()
{
    BuildLOG2();
    Buildones();
    while(cin>>str,str[0]!='e'){
        init();
        //i,j是九宮格的行列座標,k是字符串中的線性座標
        int cellsLeft = 0;
        for(int i=0,k=0;i<N9;i++)
            for(int j=0;j<N9;j++,k++)
                if(str[k]!='.'){
                    int t=str[k]-'1';  //把1-9映射到 0-8
                    flipbits(i,j,t);  //初始化是1,這裏取反設置爲0,表示該位已經佔用
                }
                else cellsLeft++;
        dfs(cellsLeft);   
    }
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章