洛谷P1074 [Noip2009]靶形数独

题目描述

  • 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他们想用数独来一比高低。但普通的数独对他们来说都过于简单了,于是他们向Z博士请教,Z博士拿出了他最近发明的“靶形数独”,作为这两个孩子比试的题目。
  • 靶形数独的方格同普通数独一样,在9格宽×9格高的大九宫格中有9个3格宽×3格高的小九宫格(用粗黑色线隔开的)。在这个大九宫格中,有一些数字是已知的,根据这些数字,利用逻辑推理,在其他的空格上填入1到9的数字。每个数字在每个小九宫格内不能重复出现,每个数字在每行、每列也不能重复出现。但靶形数独有一点和普通数独不同,即每一个方格都有一个分值,而且如同一个靶子一样,离中心越近则分值越高。(如图)
  • 上图具体的分值分布是:最里面一格(黄色区域)为10分,黄色区域外面的一圈(红色区域)每个格子为9分,再外面一圈(蓝色区域)每个格子为8分,蓝色区域外面一圈(棕色区域)每个格子为7分,最外面一圈(白色区域)每个格子为6分,如上图所示。
  • 比赛的要求是:每个人必须完成一个给定的数独(每个给定数独可能有不同的填法),而且要争取更高的总分数。而这个总分数即每个方格上的分值和完成这个数独时填在相应格上的数字的乘积的总和
  • 如图,在以下的这个已经填完数字的靶形数独游戏中,总分数为2829。游戏规定,将以总分数的高低决出胜负。
  • 由于求胜心切,小城找到了善于编程的你,让你帮他求出,对于给定的靶形数独,能够得到的最高分数。

输入输出格式

输入格式

  • 一共9行。每行9个整数(每个数都在0―9的范围内),表示一个尚未填满的数独方格,未填的空格用“0”表示。每两个数字之间用一个空格隔开。

输出格式

  • 输出可以得到的靶形数独的最高分数。如果这个数独无解,则输出整数-1。

输入输出样例

输入样例#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

输入样例#2

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

输出样例#1

2829

输出样例#2

2852

说明

数据范围

  • 40%的数据,数独中非 0 数的个数不少于 30。
  • 80%的数据,数独中非 0 数的个数不少于 26。
  • 100%的数据,数独中非 0 数的个数不少于 24。

分析 搜索+贪心+剪枝

40pts

  • 该题主要就是填数独的问题,最简单的思路就是按照八皇后那样用 bool 数组记录每行、每列、每个九宫格内已经出现的数字,将没填的格子用数组记录下来,一格子一格子搜索即可,判断九宫格和求所得分数可以用常量数组简单表示

100pts

  • 事实上对于数独问题,我们通常有如下剪枝:
    1.类似手填数独,每次我们优先选取可选数字最少的那一个格子填数,能够很大程度降低搜索层数
    2.在填可选数字最少(可选数字个数 2 )的那一个格子之前,我们每次将只剩下一个可选数字的格子先填上,也避免了不必要的枚举和搜索(这里要注意下回溯,详细见代码)

代码

#include <iostream>
#include <cstdio>

using namespace std;

const int N = 9, M = 10, L = M * M;
const int Maxn = 0x3f3f3f3f;
bool h[M][M], l[M][M], b[M][M], stp[L];
int Gx[L], Gy[L], cnt, num, Ans, now;

const int Val[N][N] = {{6, 6, 6, 6, 6, 6, 6, 6, 6},
                       {6, 7, 7, 7, 7, 7, 7, 7, 6},
                       {6, 7, 8, 8, 8, 8, 8, 7, 6},
                       {6, 7, 8, 9, 9, 9, 8, 7, 6},
                       {6, 7, 8, 9, 10, 9, 8, 7, 6},
                       {6, 7, 8, 9, 9, 9, 8, 7, 6},
                       {6, 7, 8, 8, 8, 8, 8, 7, 6},
                       {6, 7, 7, 7, 7, 7, 7, 7, 6},
                       {6, 6, 6, 6, 6, 6, 6, 6, 6}};

const int Area[N][N] = {{1, 1, 1, 2, 2, 2, 3, 3, 3},
                        {1, 1, 1, 2, 2, 2, 3, 3, 3},
                        {1, 1, 1, 2, 2, 2, 3, 3, 3},
                        {4, 4, 4, 5, 5, 5, 6, 6, 6},
                        {4, 4, 4, 5, 5, 5, 6, 6, 6},
                        {4, 4, 4, 5, 5, 5, 6, 6, 6},
                        {7, 7, 7, 8, 8, 8, 9, 9, 9},
                        {7, 7, 7, 8, 8, 8, 9, 9, 9},
                        {7, 7, 7, 8, 8, 8, 9, 9, 9}};

inline void CkMax(int &x, int y) {if (x < y) x = y;}

inline void Dfs(int nowVal)
{
    int stk[L], Pnum[L];
    int res = Maxn, k = 0, top = 0, Only;
    for (int i = 1; i <= num; ++i)
     if (!stp[i]) 
     {
        cnt = 0;
        int x = Gx[i], y = Gy[i], z = Area[x][y];
         for (int j = 1; j <= 9; ++j) 
          if (!h[x][j] && !l[y][j] && !b[z][j]) ++cnt, Only = j;
        if (!cnt) return ;
        if (cnt == 1) stk[++top] = i, Pnum[top] = Only;
         else if (res > cnt) res = cnt, k = i;
     }

    for (int i = 1; i <= top; ++i)
    {
        int x = Gx[stk[i]], y = Gy[stk[i]], z = Area[x][y], t = Pnum[i];
        if (h[x][t] || l[y][t] || b[z][t]) 
        {
            for (int j = 1; j < i; ++j)
            {
                x = Gx[stk[j]]; y = Gy[stk[j]]; z = Area[x][y]; t = Pnum[j];
                stp[stk[j]] = false; 
                h[x][t] = l[y][t] = b[z][t] = false; nowVal -= Val[x][y] * t; 
            }
            return ;
        }
        stp[stk[i]] = true;
        h[x][t] = l[y][t] = b[z][t] = true; nowVal += Val[x][y] * t; 
    }

    if (!k) 
    {
        CkMax(Ans, nowVal);
        for (int i = 1; i <= top; ++i)
        {
            int x = Gx[stk[i]], y = Gy[stk[i]], z = Area[x][y], t = Pnum[i];
            stp[stk[i]] = false;
            h[x][t] = l[y][t] = b[z][t] = false; nowVal -= Val[x][y] * t; 
        }
        return ;
    }

    int x = Gx[k], y = Gy[k], z = Area[x][y];
    stp[k] = true; 
    for (int i = 9; i >= 1; --i)
    if (!h[x][i] && !l[y][i] && !b[z][i])
    {
        h[x][i] = l[y][i] = b[z][i] = true;
        Dfs(nowVal + Val[x][y] * i);
        h[x][i] = l[y][i] = b[z][i] = false;
    }
    stp[k] = false;

    for (int i = 1; i <= top; ++i)
    {
        int x = Gx[stk[i]], y = Gy[stk[i]], z = Area[x][y], t = Pnum[i];
        stp[stk[i]] = false;
        h[x][t] = l[y][t] = b[z][t] = false; nowVal -= Val[x][y] * t; 
    }
}

int main()
{
    int x, y, z;
    for (int i = 0; i < N; ++i)
     for (int j = 0; j < N; ++j)
     {
          scanf("%d", &x);
          if (!x) Gx[++num] = i, Gy[num] = j;
          else 
          {
              now += Val[i][j] * x;
              h[i][x] = l[j][x] = b[Area[i][j]][x] = true;
          }
     } 
    Dfs(now);
    printf("%d\n", Ans ? Ans : -1);
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值