dfs+剪枝求解靶形数独(二进制优化)

题目描述

蒜头君天资聪颖,酷爱数学,尤其擅长做数独游戏。不过普通的数独游戏已经满足不了蒜头君了,于是他发明了一种“金字塔数独”:
下图即为金字塔数独。和普通数独一样,在9 * 9的大九宫格中有 9个3 * 3的小九宫格(用粗黑色线隔开的)。要求每个格子上都有一个 1 到 9 的数字,每个数字在每个小九宫格内不能重复出现,每个数字在每行、每列也不能重复出现。
但金字塔数独的每一个格子都有一个分值,类似金字塔的俯视图。如图所示,白色格子为 6 分,蓝色格子为 7 分,绿色格子为 8 分,紫色格子为 9 分,红色格子为 10 分。颜色相同的格子分值一样,离中心越近则分值越高。
在这里插入图片描述
金字塔数独的总分等于每个格子上的数字和对应的分值乘积之和。现在蒜头君给定金字塔数独的若干数字,请问如何填写,可以使得金字塔数独的总分最高。

Input

输入一共9行。每行输入9个整数(每个数都在0-9 的范围内),每两个整数之间用一个空格隔开,“0”表示该格子为空。

Output

输出为一行,输出一个整数,代表金字塔数独的最高总分。如果数独无解,则输出-1。

样例

Input:
0 0 0 0 0 0 0 0 0
0 0 3 0 0 0 9 0 0
7 9 0 0 0 2 0 1 3
0 0 9 1 5 0 0 0 0
0 7 4 0 2 6 1 3 9
0 0 6 0 0 0 0 0 0
6 0 0 0 0 7 0 0 0
3 1 0 4 0 5 7 9 6
0 0 7 0 0 1 0 4 0

Output:
2864

题目分析

数独类型的题目都是典型的用dfs来解,但这题单纯用dfs会TLE,我也是看了b站上一位大佬的视频讲解才懂了。这题要用二进制的方法来优化。用两个一维数组分别储存每一行和每一列的填数情况,再用一个二维数组分别储存每一宫的填数情况。
例如:100111111表示7和8(从左往右第7位和第8位为0)还未填,其他数已填。
然后开两个一维数组作"小抄",ones用来储存1到(1<<9)(即2^8)中每个数字(二进制)含多少个1,map用来储存一个数(二进制)为1的最高位是第几位。
然后就是lowbit运算,用以返回x在二进制表示下最低位的1以及它后面的0构成的数值。原理就不过多解释了。
例如:110010100经过运算后得到100。
将lowbit与map数组搭配就可以得到一个位置的可填数字了。

#include<iostream>
using namespace std;
int a[10][10], row[9], col[9], cell[3][3];
int ones[1 << 9], map[1 << 9], Max = 0;
int score(int x, int y) //每一圈所得分数
{
    if(x == 0 || x == 8 || y == 0 || y == 8)
        return 6;
    if((x > 0 && x < 8 && (y == 1 || y == 7)) || (y > 0 && y < 8 && (x == 1 || x == 7)))
        return 7;
    if((x > 1 && x < 7 && (y == 2 || y == 6)) || (y > 1 && y < 7 && (x == 2 || x == 6)))
        return 8;
    if((x > 2 && x < 6 && (y == 3 || y == 5)) || (y > 2 && y < 6 && (x == 3 || x == 5)))
        return 9;
    if(x == 4 && y == 4)
        return 10;
}
int intersection(int x, int y) //取该点可填数字的交集
{
    return row[x] & col[y] & cell[x / 3][y / 3];
}
int lowbit(int x) // 返回x在二进制表示下最低位的1以及它后面的0构成的数值
{
    return x & -x;
}
void dfs(int step)
{
    int i, j, x, y, Min = 10;
    if(!step) //全部填完后记下所得分数,然后回溯
    {
        int sum = 0;
        for(int k = 0; k < 9; k++)
        {
            for(int l = 0; l < 9; l++)
            {
                sum += a[k][l] * score(k, l);
            }
        }
        Max = max(Max, sum);
        return;
    }
    for(i = 0; i < 9; i++) //寻找可填数字最少的位置 ,从该位置开始深搜
        for(j = 0; j < 9; j++)
            if(!a[i][j])
            {
                int sum = ones[intersection(i, j)];
                if(sum < Min)
                {
                    Min = sum;
                    x = i;
                    y = j;
                }
            }
    for(i = intersection(x, y); i > 0; i -= lowbit(i))
    {
        int s = map[lowbit(i)];
        a[x][y] = s + 1; //加上一是因为map数组储存的是0到8,而要填的数字是1到9,因此要把0~8映射成1~9
        row[x] -= 1 << s;
        col[y] -= 1 << s;
        cell[x / 3][y / 3] -= 1 << s; //填上一个数后将该位标记为0
        dfs(step - 1);
        a[x][y] = 0;
        row[x] += 1 << s;
        col[y] += 1 << s;
        cell[x / 3][y / 3] += 1 << s; //还原
    }
}
void init() //初始化
{
    int i, j;
    for(i = 0; i < 9; i++)
    {
        row[i] = (1 << 9) - 1;
        col[i] = (1 << 9) - 1; //记得加括号 ,<<优先级低于-。
    }
    for(i = 0; i < 3; i++)
        for(j = 0; j < 3; j++)
            cell[i][j] = (1 << 9) - 1;
    //将每一行每一列每一宫都初始化为111111111,表示每个数字都可使用
    for(i = 0; i < 1 << 9; i++)
    {
        int sum = 0;
        for(j = i; j > 0; j -= lowbit(j))
            sum++;
        ones[i] = sum;
    }
    //ones数组用来储存1到(1<<9)(即2^8)中每个数字(二进制)含多少个1
    for(i = 0; i < 9; i++)
        map[1 << i] = i;
    //map数组用来储存一个数(二进制)为1的最高位是第几位。  
}
int main()
{
    int i, j, count = 0;
    for(i = 0; i < 9; i++)
        for(j = 0; j < 9; j++)
            cin >> a[i][j];
    for(i = 0; i < 9; i++)
        for(j = 0; j < 9; j++)
        {
            if(a[i][j]) //将已填数字所在行,列,宫的该位标记为0
            {
                int k = a[i][j] - 1;//同理,把1~9映射成0~8
                row[i] -= 1 << k;
                col[j] -= 1 << k;
                cell[i / 3][j / 3] -= 1 << k;
            }
            else
                count++; //记下还有多少位置未填
        }
    dfs(count);
    if(!Max)
        cout << "-1";
    else
        cout << Max;
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值