算法實踐:數獨(3)

數獨(3)

描述

爲了尋回百年前與公主一起的記憶碎片,林克歷盡千辛萬苦總算破解了數獨試煉I和II的謎題,尋回50%的記憶碎片。

如今,擺在他面前是數獨試煉III——傳說中的靶形數獨(通過後可以獲得剩下的30%的記憶碎片)。

靶形數獨的方格同普通數獨一樣,在9×9的大九宮格中有9個3×3的小九宮格(用粗黑色線隔開的)。

在這個大九宮格中,有一些數字是已知的,根據這些數字,利用邏輯推理,在其他的空格上填入1到9的數字。

每個數字在每個小九宮格內不能重複出現,每個數字在每行、每列也不能重複出現。

但靶形數獨有一點和普通數獨不同,即每一個方格都有一個分值,而且如同一個靶子一樣,離中心越近則分值越高(如下圖所示)。

上圖具體的分值分佈是:

最裏面一格(黃色區域)爲10分
黃色區域外面的一圈(紅色區域)每個格子爲9分
再外面一圈(藍色區域)每個格子爲8分
藍色區域外面一圈(棕色區域)每個格子爲7分
最外面一圈(白色區域)每個格子爲6 分

每個人必須完成一個給定的數獨(每個給定數獨可能有不同的填法),而且要爭取更高的總分數。

而這個總分數即每個方格上的分值和完成這個數獨時填在相應格上的數字的乘積的總和。

如圖,在以下的這個已經填完數字的靶形數獨遊戲中,總分數爲2829。

遊戲規定,將以總分數的高低決出勝負。

靶子2.jpe.jpg

求對於給定的靶形數獨,能夠得到的最高分數。

輸入

輸入一共包含9行。

每行 9 個整數(每個數都在 0—9 的範圍內),表示一個尚未填滿的數獨方格,未填的空格用“0”表示。

每兩個數字之間用一個空格隔開。

輸出

輸出可以得到的靶形數獨的最高分數。

如果這個數獨無解,則輸出整數-1。

輸入樣例

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

輸出樣例

2829

難度

極難

解法

相較數獨2增加一個計分函數get_score

代碼

#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 Sudu[N9][N9];
int maxScore = -1;
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_score(int x,int y,int t){
    return (min(min(x,8-x),min(y,8-y))+6)*t;
}
//?
//取交集的運算
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
}

//?-------------
inline void Set(int x,int y,int t){
    Sudu[x][y] = t;
}
//?-------------

void printStr2line(string s){
    cout<<s<<endl;
}
//!========================================================================
void dfs(int cellsLeft,int score){
    if(!cellsLeft){
        //輸出可行解
        maxScore = max(maxScore,score);
        return;
    }
    //找出可選方案數最小的格子
    int minv = 10;
    int x,y;
    for(int i=0;i<N9;i++)
        for(int j=0;j<N9;j++)
            if(!Sudu[i][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 i=get(x,y);i;i-=lowbit(i)){
        int t=LOG2[lowbit(i)];  //得到1的最低位位置
        Set(x,y,t+1);
        //修改狀態row,col,cell
        flipbits(x,y,t);
        // str[x*9+y] = '1'+t;  //修改str對應位置上的數(0-8)映射到(1-9)
        dfs(cellsLeft-1,score+get_score(x,y,t+1));
        //恢復狀態
        // str[x*9+y]='.';
        flipbits(x,y,t);
        Set(x,y,0);
    }
    // return;
}
//! ========================================================
int main()
{
    BuildLOG2();
    Buildones();
    init();
    int cellsLeft = 0,score = 0;
    for(int i=0;i<N9;i++)
        for(int j=0;j<N9;j++){
            int t; cin>>t;
            if(t){
                Set(i,j,t);
                if(t>0) flipbits(i,j,t-1);
                score += get_score(i,j,t);
            }
            else cellsLeft++;
        }
    dfs(cellsLeft,score);
    cout<<maxScore<<endl;
    return 0;
}

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