算法实践:数独(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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值