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

【输入输出样例 1】

2852

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

-------------------------------------------------------分割线--------------------------------------------------------------------


真是一道神奇的搜索题!很容易发现,直接爆搜会超时。(废话)

除了加上一些必要的横、列、方格的判断之外,我想到了一个很好的剪枝。(其实是参考题解的O(∩_∩)O~~)

像上次的prime一样,我们可以优先填那些限制比较多的格子——所以在爆搜之前,先做一下预处理,把每个待填的方格按限制多少来排序;然后根据顺序来爆搜。

然而自己测了一下,发现20个点中超时了3个点!看来,必须还要有一个强有力的剪枝。众大牛普遍反映可以用位运算优化常数、加上inline或者是用更加高级的dancing links。

通过再次研究网上的题解,我发现自己的预处理有点问题。试想:当找到一个限制最大的0时,该格子还会对该行、该列、该九宫格增添新的限制。于是我在预处理的时候就一点一点找,每次找到一个限制最大的0后,更新限制再去找。

85分代码:

#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;
const int s[10][10]={0,0,0,0,0,0,0,0,0,0,0,1,1,1,2,2,2,3,3,3,
0,1,1,1,2,2,2,3,3,3,0,1,1,1,2,2,2,3,3,3,0,4,4,4,5,5,5,6,6,6,
0,4,4,4,5,5,5,6,6,6,0,4,4,4,5,5,5,6,6,6,0,7,7,7,8,8,8,9,9,9,
0,7,7,7,8,8,8,9,9,9,0,7,7,7,8,8,8,9,9,9};
const int p[10][10]={0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,6,6,6,6,6,
0,6,7,7,7,7,7,7,7,6,0,6,7,8,8,8,8,8,7,6,0,6,7,8,9,9,9,8,7,6,
0,6,7,8,9,10,9,8,7,6,0,6,7,8,9,9,9,8,7,6,0,6,7,8,8,8,8,8,7,6,
0,6,7,7,7,7,7,7,7,6,0,6,6,6,6,6,6,6,6,6};
bool line[10][10],row[10][10],square[10][10];
int xx,yy,n,num,i,j,ans,now,last,cnt,a[10][10];
struct arr{int x,y,z,f[10];}order[100];
bool cmp(arr a,arr b) {return a.z<b.z||a.z==b.z&&p[a.x][a.y]>p[b.x][b.y];}
inline void get_square(int i,int j)
{
  if (i<=3) xx=1;
  else if (i<=6) xx=4;
  else xx=7;
  if (j<=3) yy=1;
  else if (j<=6) yy=4;
  else yy=7;
} 
inline void get_order()
{
  int temp,x,y,max=0,k=0;bool t[10];
  for (int i=1;i<=9;i++)
    for (int j=1;j<=9;j++)
      if (a[i][j]==0)
      {
        memset(t,1,sizeof(t));temp=0;
        for (xx=1;xx<=9;xx++)
          if (a[xx][j]>0) t[a[xx][j]]=false;
        for (yy=1;yy<=9;yy++)
          if (a[i][yy]>0) t[a[i][yy]]=false;
        get_square(i,j);
        for (int i1=xx;i1<=xx+2;i1++)
          for (int j1=yy;j1<=yy+2;j1++)
            if (a[i1][j1]>0) t[a[i1][j1]]=false;
        k++;cnt=0;
        for (xx=1;xx<=9;xx++)
          if (t[xx]) {temp++;order[k].f[0]++;order[k].f[order[k].f[0]]=xx;}
        order[k].x=i;order[k].y=j;order[k].z=temp;
      }
  sort(order+1,order+k+1,cmp);num=k;
}  
inline void dfs(int k)
{
  if (k==num+1)
  {
    if (now>ans) ans=now;
    return;
  }  
  int x=order[k].x;int y=order[k].y;
  for (int j=1;j<=order[k].f[0];j++)
    {
    int i=order[k].f[j];
    if (line[x][i]&&row[y][i]&&square[s[x][y]][i])
    {
      now+=i*p[x][y];
      line[x][i]=false;row[y][i]=false;square[s[x][y]][i]=false;
      dfs(k+1);
      now-=i*p[x][y];
      line[x][i]=true;row[y][i]=true;square[s[x][y]][i]=true;
    }
    }
}
int main()
{
  memset(line,1,sizeof(line));
  memset(row,1,sizeof(row));
  memset(square,1,sizeof(square));
  for (i=1;i<=9;i++)
    for (j=1;j<=9;j++)
    {
      scanf("%ld",&a[i][j]);
      if (a[i][j]>0) 
      {
        line[i][a[i][j]]=false;
        row[j][a[i][j]]=false;
        square[s[i][j]][a[i][j]]=false;
        last+=a[i][j]*p[i][j];
      }
    }
  get_order();
  ans=-1;dfs(1);
  if (ans==-1) last=0;
  printf("%ld\n",ans+last);
  return 0;
}

最后AC代码:

#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;
const int s[10][10]={0,0,0,0,0,0,0,0,0,0,0,1,1,1,2,2,2,3,3,3,
0,1,1,1,2,2,2,3,3,3,0,1,1,1,2,2,2,3,3,3,0,4,4,4,5,5,5,6,6,6,
0,4,4,4,5,5,5,6,6,6,0,4,4,4,5,5,5,6,6,6,0,7,7,7,8,8,8,9,9,9,
0,7,7,7,8,8,8,9,9,9,0,7,7,7,8,8,8,9,9,9};                      //这是每个九宫格的预处理。
const int p[10][10]={0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,6,6,6,6,6,
0,6,7,7,7,7,7,7,7,6,0,6,7,8,8,8,8,8,7,6,0,6,7,8,9,9,9,8,7,6,
0,6,7,8,9,10,9,8,7,6,0,6,7,8,9,9,9,8,7,6,0,6,7,8,8,8,8,8,7,6,
0,6,7,7,7,7,7,7,7,6,0,6,6,6,6,6,6,6,6,6};                     //分值
bool line[10][10],row[10][10],square[10][10],check[10][10];
int xx,yy,n,num,i,j,ans,k,m,now,last,cnt,a[10][10],f[10][10]; //f就是限制
struct arr{int x,y;}order[100];                               //order是枚举顺序。
inline void get_square(int i,int j)                           //找到当前i,j的九宫格的左上角
{
  if (i<=3) xx=1;
  else if (i<=6) xx=4;
  else xx=7;
  if (j<=3) yy=1;
  else if (j<=6) yy=4;
  else yy=7;
} 
inline void get_order(int k)                                 //每次寻找限制第k多的0的位置
{
  int max=0,x,y,i,j;
  for (i=1;i<=9;i++)
    for (j=1;j<=9;j++)
      if (check[i][j]&&f[i][j]>max) 
        {max=f[i][j];x=i;y=j;}
  order[k].x=x;order[k].y=y;
  for (i=1;i<=9;i++)
  {f[i][y]++;f[x][i]++;}
  get_square(x,y);
  for (i=xx;i<=xx+2;i++)
    for (j=yy;j<=yy+2;j++)
      f[i][j]++;
  check[x][y]=false;
}  
inline void dfs(int k)                                       //容易理解的dfs       
{
  if (k==num+1)
  {
    if (now>ans) ans=now;
    return;
  }  
  int x=order[k].x;int y=order[k].y;
  for (int i=1;i<=9;i++)
    if (line[x][i]&&row[y][i]&&square[s[x][y]][i])                        
    {
      now+=i*p[x][y];
      line[x][i]=false;row[y][i]=false;square[s[x][y]][i]=false;
      dfs(k+1);
      now-=i*p[x][y];
      line[x][i]=true;row[y][i]=true;square[s[x][y]][i]=true;
    }
}
int main()
{
  memset(line,1,sizeof(line));
  memset(row,1,sizeof(row));
  memset(square,1,sizeof(square));
  for (i=1;i<=9;i++)
    for (j=1;j<=9;j++)
    {
      scanf("%ld",&a[i][j]);
      if (a[i][j]>0) 
      {
        line[i][a[i][j]]=false;
        row[j][a[i][j]]=false;
        square[s[i][j]][a[i][j]]=false;
        last+=a[i][j]*p[i][j];                              //原来已经填好的分值
        for (k=1;k<=9;k++)
        {
          f[i][k]++;f[k][j]++;
        }
        get_square(i,j);
        for (k=xx;k<=xx+2;k++)
          for (m=yy;m<=yy+2;m++)
            f[k][m]++;                                   
      }
      else {num++;check[i][j]=true;}
    }
  for (i=1;i<=num;i++) get_order(i);
  ans=-1;dfs(1);
  if (ans==-1) last=0;
  printf("%ld\n",ans+last);
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值