codevs1174 靶形数独

Posted ACforever

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了codevs1174 靶形数独相关的知识,希望对你有一定的参考价值。

题目描述 Description

小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他
们想用数独来一比高低。但普通的数独对他们来说都过于简单了,于是他们向Z 博士请教,
Z 博士拿出了他最近发明的“靶形数独”,作为这两个孩子比试的题目。
靶形数独的方格同普通数独一样,在 9 格宽×9 格高的大九宫格中有9 个3 格宽×3 格
高的小九宫格(用粗黑色线隔开的)。在这个大九宫格中,有一些数字是已知的,根据这些

数字,利用逻辑推理,在其他的空格上填入1 到9 的数字。每个数字在每个小九宫格内不能
重复出现,每个数字在每行、每列也不能重复出现。但靶形数独有一点和普通数独不同,即
每一个方格都有一个分值,而且如同一个靶子一样,离中心越近则分值越高。

 

上图具体的分值分布是:最里面一格(黄色区域)为 10 分,黄色区域外面的一圈(红
色区域)每个格子为9 分,再外面一圈(蓝色区域)每个格子为8 分,蓝色区域外面一圈(棕
色区域)每个格子为7 分,最外面一圈(白色区域)每个格子为6 分,如上图所示。比赛的
要求是:每个人必须完成一个给定的数独(每个给定数独可能有不同的填法),而且要争取
更高的总分数。而这个总分数即每个方格上的分值和完成这个数独时填在相应格上的数字
的乘积的总和。如图,在以下的这个已经填完数字的靶形数独游戏中,总分数为2829。游
戏规定,将以总分数的高低决出胜负。

由于求胜心切,小城找到了善于编程的你,让你帮他求出,对于给定的靶形数独,能
够得到的最高分数。

技术分享
输入描述 Input Description

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

输出描述 Output Description

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

样例输入 Sample Input

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

样例输出 Sample Output

【输入输出样例 1】

2829

【输入输出样例 1】

2852

数据范围及提示 Data Size & Hint

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

思路:
(摘自sth课件)

我们看每个空白的格子,看看它还可以填几个数。 如果是我们在做数独的话,我们显然会先从可以填的数少的格子开始试。 所以我们可以找出每个格子可以填几个数,然后排序,先从可以填的数少的格子开始搜。

可行性剪枝:显然,这道题的不合法解就是同一行或同一列或同一个九宫格出现了相同数字,所以我们只需要知道一个格子可以填哪些数,然后只搜索这些可以填的数就可以了

最优性剪枝: 如果当前已有得分+未来可能得到的最大的分<=当前已得到的最大总分(已经找到的合法解中的最优解),则直接退出。显然未来得分不可能超过(没有填的格子数*90)。

我们再想一下,爆搜的大部分时间都浪费在了哪? 在找这个格子能够填哪些数。 这个问题能够快速解决吗? 搜索问题中,解决这种问题有一个通用方法,就是:位运算!

既然每行每列每个九宫格一共就9个数,我们就记录一下哪些数没用过。 一共9个数,想到了啥? 2^9 压位! 若第i位的二进制为1,则表示第i个数在这一行/这一列/这个九宫格还没有出现过,还可以使用。

那我们怎么找一个格子所在的该行该列该九宫格都还未出现过的数字? 三个信息and一下就可以了。 假设得到的是x,那么,x的二进制上为1的就是我们可以在这个格子里填的数字。

怎么遍历可以填的所有数字?也就是x的所有二进制位1的位?显然不能一位一位的遍历。。。。那样就成了暴力了。。。。我们希望只遍历二进制是1的位。 For (;x!=0;x-=x&-x) { y=log[x&-x]+1;//+1是因为数字是1~9,不是0~8 ……. ……. }

注意,在给这个格子选择某个数填上,dfs进入下一层之前,我们要先修改该行该列该九宫格还能填的剩余数字。 Dfs回到上一层之后,也记得修改该行该列该九宫格还能填的剩余数字。 For (;x!=0;x-=x&-x) { y=log[x&-x]+1; 记录这个格子填y,将该格子得分加入当前总分,并将该行该列该九宫格记录的还能填的数字信息and (2^10-1-(x&-x)) dfs(………); 将该格子得分从当前总分中减去,并将该行该列该九宫格记录的还能填的数字信息or (x&-x) }

代码:

①自己写的,用了map

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cmath> 
#include<algorithm>
#include<vector>
#define mx 111
#define maxint 10000
using namespace std;
struct Node{
    int x;
    int y;
    int a;
};
vector<Node> node;
int a[mx][mx],line[mx],col[mx],pal[mx],block[mx][mx],ok[mx][mx],sub[mx],Log[maxint],lft,maxans,ansleft;
int s_jud[9][9] = {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,};
int g_jud[9][9] = {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,};
bool cmp(Node x,Node y){
    return x.a < y.a;
}
void init(){
    lft = maxans = ansleft = 0;
    int vd = pow(2,9) - 1,acc;
    memset(line,vd,sizeof(line));
    memset(col,vd,sizeof(col));
    memset(block,vd,sizeof(block));
    memset(pal,vd,sizeof(pal));
    memset(ok,0,sizeof(ok));
    for(int c = 1,x = 0;c < maxint;c *= 2,x++) Log[c] = x;
    for(int i = 1;i <= 9;i++) sub[i] = vd - pow(2,i-1);
    for(int i = 1; i <= 9;i++){
        for(int j = 1;j <= 9;j++){
            cin>>a[i][j];
            if(a[i][j]){
                lft++;
                line[i] = line[i] & sub[a[i][j]];
                col[j] = col[j] & sub[a[i][j]];
                pal[g_jud[i-1][j-1]] = pal[g_jud[i-1][j-1]] & sub[a[i][j]];
                ok[i][j] = 1;
                ansleft += a[i][j] * s_jud[i-1][j-1];
             }
        }
    }
    lft = 81 - lft;
    Node temp;
    for(int i = 1;i <= 9;i++){
        for(int j = 1;j <= 9;j++){
            block[i][j] = line[i] & col[j] & pal[g_jud[i-1][j-1]];
            acc = 0;
            for(int x = block[i][j];x!=0;x-=x&-x) acc++;
            if(ok[i][j]) continue;
            temp.y = i;
            temp.x = j;
            temp.a = acc;
            node.push_back(temp);
            
        }
    }
    sort(node.begin(),node.end(),cmp);
}
void opt_init(){
    for(int i = 0;i <lft;i++){
        cout<<"No."<<i<<" ("<<node[i].x<<","<<node[i].y<<") "<<node[i].a<<endl;
    }
}
int dfs(int deep,int score){
    if(deep > lft){
        maxans = max(score,maxans);
        return score;
    }
    
    int nowx = node[deep-1].x,nowy = node[deep-1].y,test;
    int x = line[nowy] & col[nowx] & pal[g_jud[nowy-1][nowx-1]],y;
    for(;x!=0;x-=x&-x){
        y=Log[x&-x]+1;
        score += y * s_jud[nowy-1][nowx-1];
        test = pow(2,10) - 1 - (x&-x);
        line[nowy] = line[nowy] & test;
        col[nowx] = col[nowx] & test;
        pal[g_jud[nowy-1][nowx-1]] = pal[g_jud[nowy-1][nowx-1]] & test;
        dfs(deep+1,score);
        score -= y * s_jud[nowy-1][nowx-1];
        line[nowy] = line[nowy] | (x&-x);
        col[nowx] = col[nowx] | (x&-x);
        pal[g_jud[nowy-1][nowx-1]] = pal[g_jud[nowy-1][nowx-1]] | (x&-x);
      }
}
int main(){
    init();
    dfs(1,0);
    if(maxans) cout<<maxans + ansleft<<endl;
    else cout<<-1<<endl;
    return 0;
}

②ida*

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#define MAX(a,b) a>b?a:b
using namespace std;

int Map[10][10]   = {0};   //这个就不用解释了吧
int Nine[10][10]  = {0};    //小九宫格里每个数的使用标记,小九宫格的编号左到右,上到下,1,2,3,4,5,6,7,8,9
int Line[10][10]  = {0};    //行上每个数的使用标记
int Row[10][10]   = {0};   //列上每个数的使用标记
int FQueL[10]     = {0};    //被启发后的行访问顺序
int FQueR[10]     = {0};    //被启发后的列访问顺序
int Value[10][10] = {0};    //地图各点的价值因子
int QueL[81]      = {0};     //
int QueR[81]      = {0};     //与上面那个,是根据启发后制定的搜索顺序的行列参数
int QLen  = 0;                  //搜索队列的长度
int Ans   = -1;                 //最优解
int Count = 0;                  //废物变量,纪念原来我错误的写法,懒得删掉了

int Swap(int *a,int *b)
{
  int o=*a;
  *a=*b;
  *b=o;
}

void Heu()  //启发函数
{
  for(int i=1;i<=9;++i){
    FQueL[i] = i;
    FQueR[i] = i;
    }
  
  for(int i=1;i<=8;++i)
    for(int j=9;j>=i+1;--j){
      if(Line[FQueL[j]][0] > Line[FQueL[j-1]][0])
          Swap(&FQueL[j],&FQueL[j-1]);
      if(Row[FQueR[j]][0] > Row[FQueR[j-1]][0])
          Swap(&FQueR[j],&FQueR[j-1]);
    } 
  for(int i=1;i<=9;++i)
    for(int j=1;j<=9;++j)
      if(Map[FQueL[i]][FQueR[j]] == 0){
          QueL[QLen]=FQueL[i];
          QueR[QLen]=FQueR[j];
          QLen++;
      }  
// for(int i=1;i<=9;++i) 
//      cout<<FQueL[i]<<‘ ‘<<Line[FQueL[i]][0]<<‘ ‘<<FQueR[i]<<‘ ‘<<Row[FQueR[i]][0]<<endl;
} 

int belong(int i,int j)     //判断行列参数为i,j的点属于哪一个九宫格。
{
   int xt = 0,yt = 0;
   for(int k=6;k>=0;k-=3){
    if(i-k>0){xt=(k+3)/3;break;}
   }
   for(int k=6;k>=0;k-=3){
    if(j-k>0){yt=(k+3)/3;break;}
   }
//   cout<<xt<<‘ ‘<<yt<<‘ ‘<<xt+(yt-1)*3<<endl;
//   cout<<i<<‘ ‘<<j<<‘ ‘<< yt+(xt-1)*3 <<endl;
   return yt+(xt-1)*3;
}

int Score()           //成绩计算
{
  int Temp = 0;
  for(int i=1;i<=9;++i)
    for(int j=1;j<=9;++j)
      Temp += Map[i][j]*Value[i][j];
  Ans = MAX(Temp,Ans);
//  cout<<Ans<<endl;
}

int Dfs(int step)      //深度优先搜索主体
{
//  cout<<step<<‘ ‘<<81-Count<<endl;
//  for(int i=1;i<=9;++i,cout<<endl)
//    for(int j=1;j<=9;++j)
//      cout<<Map[i][j]<<‘ ‘;
  if(step == QLen){Score();return 0;}
       if(!Map[QueL[step]][QueR[step]])
       for(int k=1;k<=9;++k){
             if(!Nine[belong(QueL[step],QueR[step])][k])
               if(!Line[QueL[step]][k] && !Row[QueR[step]][k]){
                //Heu();
//                cout<<FQueL[i]<<‘ ‘<<FQueR[j]<<‘ ‘<<k<<endl;
                   Map[QueL[step]][QueR[step]] = k;
                   Nine[belong(QueL[step],QueR[step])][k] = 1;
                   Line[QueL[step]][k]=Row[QueR[step]][k] = 1;
                   Line[QueL[step]][0]++;Row[QueR[step]][0]++;
                Dfs(step+1); 
                Map[QueL[step]][QueR[step]] = 0;
                   Nine[belong(QueL[step],QueR[step])][k] = 0;
                   Line[QueL[step]][k]=Row[QueR[step]][k] = 0;
                   Line[QueL[step]][0]--;Row[QueR[step]][0]--;            
             }                 
         }
   return 0;
}

int main()
{
  for(int i=1;i<=5;++i)
    for(int j=1+(i-1);j<=9-(i-1);++j){
       Value[i][j] = 6+(i-1);
       Value[9-(i-1)][j] = 6+(i-1);
       Value[j][9-(i-1)] = 6+(i-1);
       Value[j][i] = 6+(i-1);
    }   //init value    对价值表的初始,好像其他人都是直接用{}初始的.......
//  for(int i=1;i<=9;++i,cout<<endl)
//    for(int j=1;j<=9;++j)
//       cout<<Value[i][j]<<‘ ‘;
  for(int i=1;i<=9;++i)
    for(int j=1,x,y;j<=9;++j){
       scanf("%d",&Map[i][j]);
       if(Map[i][j] != 0 ){
            Line[i][Map[i][j]] = 1;
            Line[i][0]++;
            Row[j][Map[i][j]]  = 1;
            Row[j][0]++;
            Nine[belong(i,j)][Map[i][j]] = 1;
            Count++;
       }    
     }

//  for(int i=1;i<=9;++i,cout<<endl)
//   for(int j=1;j<=9;++j)
//      cout<<Nine[i][j];

  Heu();
  Dfs(0);
  
  cout<<Ans;
  return 0;
}

 

以上是关于codevs1174 靶形数独的主要内容,如果未能解决你的问题,请参考以下文章

code1174 靶形数独

[NOIP2009提高组]靶形数独

NOIP提高组 2009 靶形数独 题解

[NOIP2009] 靶形数独

NOIP2009靶形数独

[NOIP2009]靶形数独 题解