P1074 靶形数独题解

时间:2022-04-20 08:57:27

题目描述

小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他们想用数独来一比高低。但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教,Z 博士拿出了他最近发明的“靶形数独”,作为这两个孩子比试的题目。

靶形数独的方格同普通数独一样,在 9 格宽×9 格高的大九宫格中有9 个 3 格宽×3 格高的小九宫格(用粗黑色线隔开的)。在这个大九宫格中,有一些数字是已知的,根据这些数字,利用逻辑推理,在其他的空格上填入 1 到 9的数字。每个数字在每个小九宫格内不能重复出现,每个数字在每行、每列也不能重复出现。但靶形数独有一点和普通数独不同,即每一个方格都有一个分值,而且如同一个靶子一样,离中心越近则分值越高。(如图)

P1074 靶形数独题解

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

总分数即每个方格上的分值和完成这个数独时填在相应格上的数字的乘积的总和。如图,在以下的这个已经填完数字的靶形数独游戏中,总分数为 2829。游戏规定,将以总分数的高低决出胜负。

P1074 靶形数独题解

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

输入输出格式

输入格式:

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

输出格式:

输出共 1 行。输出可以得到的靶形数独的最高分数。如果这个数独无解,则输出整数−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

输出样例#1:

2829

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

输出样例#2:

2852

说明

【数据范围】

40%的数据,数独中非 0 数的个数不少于30。

80%的数据,数独中非 0 数的个数不少于26。

100%的数据,数独中非0数的个数不少于24。

NOIP 2009 提高组 第四题


这真是一道恶心的搜索题,首先你得先熟悉数独。

玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。
————百度百科

所以我们有三个P1074 靶形数独题解数组P1074 靶形数独题解分别代表行,列,九宫格中数字使用情况。

行和列都还好,循环判断即可,但九宫格,就需要一个函数

 int ninth( int i , int j ) {
if( i <= && j <= ) return ;
if( i <= && j <= ) return ;
if( i <= ) return ;
if( i <= && j <= ) return ;
if( i <= && j <= ) return ;
if( i <= ) return ;
if( j <= ) return ;
if( j <= ) return ;
return ;
}

接着我们处理每个格子的分数,我是用函数判断,而ly用数组,最后时间来看,我还是太蒟了

 inline int check(int x,int y)
{
if(x==||y==||x==||y==)return ;
if(x==||y==||x==||y==)return ;
if(x==||y==||x==||y==)return ;
if(x==||y==||x==||y==)return ;
if(x==&&y==)return ;
} ————by cx
 int point[  ] = {  ,  ,  ,  ,  , } ;
————by ly

预处理完后便可以开始考虑如何搜索,这里我与ly有了不同的搜法。

1.ly

ly选择的是一行一行搜下去,全部答案搜出来后,再求最大值。

 int dfs( int h , int x , int y ) {
if( h == ) {
print( );
return ;
}
if( y == ) {
dfs( h + , sss[ h + ].line , );
return ;
}
if( !map[ x ][ y ] ) {
for( int i = ; i <= ; ++i ) {
if( line[ x ][ i ] == && list[ y ][ i ] == && nine[ ninth( x , y ) ][ i ] == ){
line[ x ][ i ] = , list[ y ][ i ] = , nine[ ninth( x , y ) ][ i ] = ;
map[ x ][ y ] = i ;
dfs( h , x , y + );
map[ x ][ y ] = ;
line[ x ][ i ] = , list[ y ][ i ] = , nine[ ninth( x , y ) ][ i ] = ;
}
}
}
else dfs( h , x , y + );
}

2.cx

我选择的是用一个数组存要填的的点,一个一个搜,我一开始以为我的方法会快一点,结果我被打脸了

 void dfs(int x,int y)
{
if(tot==pos-){maxn=max(ans,maxn);return;}
for(register int i=;i<=;++i)
{
if(!line[x][i]&&!list[y][i]&&!nine[ninth(x,y)][i])
{
pos++;ans+=i*check(x,y);
line[x][i]=list[y][i]=;
nine[ninth(x,y)][i]=;
dfs(b[][pos].w,b[][pos].w);
pos--;ans-=i*check(x,y);
line[x][i]=list[y][i]=;
nine[ninth(x,y)][i]=;
}
}
}

接下来便是重点,搜索剪枝

像我这种没玩过数独的乡里人,不知道玩数独有这样一个方法:

从数多的一行开始填,这样要选择的数就少了,不合法的情况就可以省掉一些

所以我们定义一个P1074 靶形数独题解(struck),用一个P1074 靶形数独题解来存每行P1074 靶形数独题解的个数。

用它作为关键字P1074 靶形数独题解一遍后,再从最少的开始搜。

ly程序:(用时: 2678ms / 内存: 920KB)

 #include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std ; inline int read( ) {//日常快读
int x = , f = ;
char c = getchar( ) ;
while( c > '' || c < '' ) {
if( c == '-' ) f = - ;
c = getchar( );
}
while( c >= '' && c <= '' ) x = x * + c - '' , c = getchar( ) ;
return f == ? x : -x ;
} int ninth( int i , int j ) {//判断i行j列在第几个九宫格里,因为九个九宫格不重复,所以某些地方可以少判断一些条件。
if( i <= && j <= ) return ;
if( i <= && j <= ) return ;
if( i <= ) return ;
if( i <= && j <= ) return ;
if( i <= && j <= ) return ;
if( i <= ) return ;
if( j <= ) return ;
if( j <= ) return ;
return ;
} int point[ ] = { , , , , , } ;//存放分数;
int map[ ][ ] ;//记录某个位置上的数;
long long ans = - ;//无解则输出-1;
struct node {
int line , sum ;
}sss[ ] ;//记录每行需要填的零的个数;
bool cmp( node i , node j ) {
return i.sum < j.sum ;
}
bool line[ ][ ] , nine[ ][ ] , list[ ][ ] ;//进行数独游戏的判断;
//为了方便观察,函数都扔下去;
int dfs( int , int , int );
int print( ); int main( )
{
for( int i = ; i <= ; ++i ) {
int k = ;
for( int j = ; j <= ; ++j ) {
map[ i ][ j ] = read( ) ;
if( !map[ i ][ j ] ) ++k;
line[ i ][ map[ i ][ j ] ] = ;
nine[ ninth( i , j ) ][ map[ i ][ j ] ] = ;
list[ j ][ map[ i ][ j ] ] = ;
}
sss[ i ].sum = k , sss[ i ].line = i ;
}
sort( sss + , sss + , cmp );
dfs( , sss[ ].line , ) ;
printf( "%lld" , ans );
return ;
}
int dfs( int h , int x , int y ) {
if( h == ) {//全部搜完了并成立,进行算分
print( );
return ;
}
if( y == ) {//为避免特判过多而加的中转;
dfs( h + , sss[ h + ].line , );
return ;
}
if( !map[ x ][ y ] ) {
for( int i = ; i <= ; ++i ) {
if( line[ x ][ i ] == && list[ y ][ i ] == && nine[ ninth( x , y ) ][ i ] == ){
line[ x ][ i ] = , list[ y ][ i ] = , nine[ ninth( x , y ) ][ i ] = ;
map[ x ][ y ] = i ;
dfs( h , x , y + );
//记得回溯
map[ x ][ y ] = ;
line[ x ][ i ] = , list[ y ][ i ] = , nine[ ninth( x , y ) ][ i ] = ;
}
}
}
else dfs( h , x , y + );
}
int print( ) {//统计当前方案的分数
long long sum = ;
for( int i = ; i <= ; ++i ) {
for( int j = ; j <= ; ++j ) {
sum += ( map[ i ][ j ] * point[ min( min( i , - i ) , min ( j , - j ) ) ] );
//越靠近中心,x与y越接近5,否则远离5
//所以min( min( i , 10 - i ) , min ( j , 10 - j ) )与
//point数组搭配即可算出当前位置的分值
}
}
ans = max( ans , sum );//更新最大值
}

cx程序:(用时: 3361ms / 内存: 928KB)

 #include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
inline int max(int x,int y){return(x)<(y)?(y):(x);}
bool line[][],list[][],nine[][];
int ans=,tot=,maxn=-,pos=,Line=,a[];
//a数组记录每行0的个数,maxn记录最大值,tot记录总共要填的点数
struct node
{int w,list;}b[][];//list记录那行0的个数
inline void read(int &x)
{
x=;int f=;
char ch=getchar();
while(ch<''||ch>'')
{if(ch=='-') f=-; ch=getchar();}
while(ch>=''&&ch<='')
{x=x*+ch-'';ch=getchar();}
x*=f;
}
inline void write(int x)
{
if(x<){putchar('-');write(~x+);}
else{if(x>)write(x/);putchar(x%+'');}
}
inline int ninth(int i,int j)
{
if(i<=&&j<=)return ;if(i<=&&j<=)return ;if(i<=)return ;
if(i<=&&j<=)return ;if(i<=&&j<=)return ;if(i<=)return ;
if(j<=)return ;if(j<=)return ;return ;
}
inline int check(int x,int y)
{
if(x==||y==||x==||y==)return ;
if(x==||y==||x==||y==)return ;
if(x==||y==||x==||y==)return ;
if(x==||y==||x==||y==)return ;
if(x==&&y==)return ;
}
void dfs(int x,int y)
{
if(tot==pos-){maxn=max(ans,maxn);return;}//搜完了
for(register int i=;i<=;++i)//判断数字1-9
{
if(!line[x][i]&&!list[y][i]&&!nine[ninth(x,y)][i])//判断数字是否被填过
{
pos++;ans+=i*check(x,y);
line[x][i]=list[y][i]=;
nine[ninth(x,y)][i]=;
dfs(b[][pos].w,b[][pos].w);
pos--;ans-=i*check(x,y);
line[x][i]=list[y][i]=;
nine[ninth(x,y)][i]=;//回溯
}
}
}
bool cmp(node i,node j)
{return i.list<j.list;}
int main()
{
bool flag1=,flag2=;
for(register int i=;i<=;++i)
{
for(register int j=;j<=;++j)
{
register int k;read(k);
if(!k)b[][tot+].w=i,b[][tot+].w=j,tot++,Line++;//代表要填
else//代表填过
{
if(i==||i==)if(j==)if(k==||k==)flag1=flag2=;
if((i==&&j==&&k==))flag1=;if(i==&&j==&&k==)flag2=;
ans+=k*check(i,j);nine[ninth(i,j)][k]=;
line[i][k]=,list[j][k]=;
}
}
a[i]=Line;Line=;//记录每行0的个数
}
if(!flag1&&!flag2)//特判,为什么后面讲了
{
for(register int i=;i<=tot;++i)b[][i].list=b[][i].list=a[b[][i].w];
std::sort(b[]+,b[]+tot+,cmp);
std::sort(b[]+,b[]+tot+,cmp);//按行中0的个数排序
}
dfs(b[][pos].w,b[][pos].w);//搜索
write(maxn);
}

因为我的搜法不同,在P1074 靶形数独题解之后仍有两个点过不去,所以我特判了一下不P1074 靶形数独题解的情况,仔细反思一下,我好像懂了。

ly是一行行搜,所以他一直搜同一行,而我存的是点,又因为P1074 靶形数独题解不稳定,我的程序可能搜完一个,又去搜另一行,所以不行,兴许可以改成P1074 靶形数独题解 或桶排,相较之下,很明显还是ly程序更优。