P1074 靶形数独题解
题目描述
小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他们想用数独来一比高低。但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教,Z 博士拿出了他最近发明的“靶形数独”,作为这两个孩子比试的题目。
靶形数独的方格同普通数独一样,在 9 格宽×9 格高的大九宫格中有9 个 3 格宽×3 格高的小九宫格(用粗黑色线隔开的)。在这个大九宫格中,有一些数字是已知的,根据这些数字,利用逻辑推理,在其他的空格上填入 1 到 9的数字。每个数字在每个小九宫格内不能重复出现,每个数字在每行、每列也不能重复出现。但靶形数独有一点和普通数独不同,即每一个方格都有一个分值,而且如同一个靶子一样,离中心越近则分值越高。(如图)
上图具体的分值分布是:最里面一格(黄色区域)为 10 分,黄色区域外面的一圈(红色区域)每个格子为9分,再外面一圈(蓝色区域)每个格子为8 分,蓝色区域外面一圈(棕色区域)每个格子为7分,最外面一圈(白色区域)每个格子为6分,如上图所示。比赛的要求是:每个人必须完成一个给定的数独(每个给定数独可能有不同的填法),而且要争取更高的总分数。而这个总分数即每个方格上的分值和完成这个数独时填在相应格上的数字的乘积的总和。
总分数即每个方格上的分值和完成这个数独时填在相应格上的数字的乘积的总和。如图,在以下的这个已经填完数字的靶形数独游戏中,总分数为 2829。游戏规定,将以总分数的高低决出胜负。
由于求胜心切,小城找到了善于编程的你,让你帮他求出,对于给定的靶形数独,能够得到的最高分数。
输入输出格式
输入格式:
一共 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,不重复。
————百度百科
所以我们有三个数组分别代表行,列,九宫格中数字使用情况。
行和列都还好,循环判断即可,但九宫格,就需要一个函数
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]=;
}
}
}
接下来便是重点,搜索剪枝
像我这种没玩过数独的乡里人,不知道玩数独有这样一个方法:
从数多的一行开始填,这样要选择的数就少了,不合法的情况就可以省掉一些
所以我们定义一个(struck),用一个来存每行的个数。
用它作为关键字一遍后,再从最少的开始搜。
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);
}
因为我的搜法不同,在之后仍有两个点过不去,所以我特判了一下不的情况,仔细反思一下,我好像懂了。
ly是一行行搜,所以他一直搜同一行,而我存的是点,又因为不稳定,我的程序可能搜完一个,又去搜另一行,所以不行,兴许可以改成 或桶排,相较之下,很明显还是ly程序更优。
P1074 靶形数独题解的更多相关文章
- P1074 靶形数独
P1074 靶形数独正着搜80分,完全倒置95分,完全倒置后左右再倒置,就会A掉,到时候脑洞要大一些. #include<iostream> #include<cstdio> ...
- 洛谷——P1074 靶形数独
P1074 靶形数独 题目描述 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他 们想用数独来一比高低.但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教, Z ...
- 洛谷P1074 靶形数独 [搜索]
题目传送门 题目描述 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他 们想用数独来一比高低.但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教, Z 博士拿出了 ...
- 洛谷 P1074 靶形数独 Label:search 不会
题目描述 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他 们想用数独来一比高低.但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教, Z 博士拿出了他最近发明的 ...
- P1074 靶形数独 dfs回溯法
题目描述 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他们想用数独来一比高低.但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教,Z 博士拿出了他最近发明的“靶 ...
- [洛谷P1074] 靶形数独
洛谷题目链接:靶形数独 题目描述 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他们想用数独来一比高低.但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教,Z 博 ...
- 洛谷 P1074 靶形数独
题目描述 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他 们想用数独来一比高低.但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教, Z 博士拿出了他最近发明的 ...
- [NOIP2009] 提高组 洛谷P1074 靶形数独
题目描述 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他 们想用数独来一比高低.但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教, Z 博士拿出了他最近发明的 ...
- luogu P1074 靶形数独
题目描述 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他 们想用数独来一比高低.但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教, Z 博士拿出了他最近发明的 ...
随机推荐
- Thymeleaf 的 onclick
th:onclick="'javascript:openBox(\''+${curCabNo}+'\',\''+${box.no}+'\')'" 真的难受 ,有没有好办法!!!!
- LeetCode算法题-Find Smallest Letter Greater Than Target(Java实现)
这是悦乐书的第306次更新,第326篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第175题(顺位题号是744).给定一个仅包含小写字母的有序字符数组,并给定目标字母目标 ...
- 每日PA -2019年1月帖-每天更新
开篇 "每日PA"有什么亮点?
- CENTOS重新安装JDK
centos 删除默认安装的JDK 重新安装JDK 1.删除JDK 通过xshell工具成功连接安装好的虚拟机之后可通过 rpm -qa | grep java 或 rpm -qa | gre ...
- 下载带有kali linux系统的VMware如何打开虚拟机?
下载带有kali linux系统的VMware如何打开虚拟机? 一.安装VMware 温馨提示:如果你对虚拟机一无所知的话,最好不要自己下载kali linux系统的ISO镜像和VMware虚拟机,然 ...
- CentOS7.x编译安装zabbix4.0
编译安装zabbix Zabbix简介 Zabbix 是一个企业级的分布式开源监控方案. Zabbix是一款能够监控各种网络参数以及服务器健康性和完整性的软件.Zabbix使用灵活的通知机制,允许用户 ...
- VMware Workstation14 安装Ubuntu18.04
1 下载Vmware Workstations14破解版 百度网盘链接:https://pan.baidu.com/s/12yVxoPCJUAmdts4SUdzndg 提取码:bs0g 2 下载Ubu ...
- Element ui 日期限制范围
时间限定范围: <el-date-picker type="date" placeholder="选择日期" v-model="addForm. ...
- 周末学习笔记——day02(带参装饰器,wraps修改文档注释,三元表达式,列表字典推导式,迭代器,生成器,枚举对象,递归)
一,复习 ''' 1.函数的参数:实参与形参 形参:定义函数()中出现的参数 实参:调用函数()中出现的参数 形参拿到实参的值,如果整体赋值(自己改变存放值的地址),实参不会改变,(可变类型)如果修改 ...
- 【C语言】位运算
编写一个函数getbits,从一个16位的单元中取出某几位(即该几位保留原值,其余位0).函数调用形式为getbits(value,n1,2).----简单题目遇到想不到的问题 c语言位运算经典问题: ...