[Luogu1074] 靶形数独

Description

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

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

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

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

Input

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

Output

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

Sample Output 1

2829

Sample Input 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 2

2852

Hint

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

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

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

题解

记录所有空的行号,列号,九宫格的号码,可填数的个数。按可填数的个数从小到大排序,再Dfs枚举。

#include<iostream>
#include<cstdio>
using namespace std;

const int N=12,N2=100;
struct node{int x,y,z,s;}K[N2];
int nk;
bool H[N][N],L[N][N],B[N][N];
int G[N][N],Ans;

void Read()
{
	for(int i=1;i<=9;++i)
		for(int j=1;j<=9;++j)
		{
			scanf("%d",&G[i][j]);
			if(G[i][j])
				H[i][G[i][j]]=L[j][G[i][j]]=B[(i-1)/3*3+(j-1)/3][G[i][j]]=1;
			else ++nk,K[nk]=(node){i,j,(i-1)/3*3+(j-1)/3};//记录行号,列号,九宫格的号码
		}
	for(int i=1;i<=nk;++i)
		for(int j=1;j<=9;++j)
			if((!H[K[i].x][j])&&(!L[K[i].y][j])&&(!B[K[i].z][j])) ++K[i].s;//计算可填数的个数
}

void Sort()//排序
{
	int k; node tmp;
	for(int i=1;i<nk;++i)
	{
		k=i;
		for(int j=i+1;j<=nk;++j)
			if(K[j].s<K[k].s) k=j;
		if(i!=k) tmp=K[i],K[i]=K[k],K[k]=tmp;
		for(int j=i+1;j<=nk;++j)
			if(K[i].x==K[j].x||K[i].y==K[j].y||K[i].z==K[j].z) --K[j].s;//此处留给读者思考
	}
}


int Calc(int x,int y)
{
	if(x==1||x==9||y==1||y==9) return 6;
	if(x==2||x==8||y==2||y==8) return 7;
	if(x==3||x==7||y==3||y==7) return 8;
	if(x==4||x==6||y==4||y==6) return 9;
	return 10;
}

void Solve()//计算
{
	int Res=0;
	for (int i=1;i<=9;++i)
		for(int j=1;j<=9;++j) Res+=G[i][j]*Calc(i,j);
	Ans=max(Ans,Res);
}

void Dfs(int id)
{
	if(id>nk) {Solve();return;}
	for(int i=1;i<=9;++i)
		if((!H[K[id].x][i])&&(!L[K[id].y][i])&&(!B[K[id].z][i]))
		{
			H[K[id].x][i]=L[K[id].y][i]=B[K[id].z][i]=1,G[K[id].x][K[id].y]=i;
			Dfs(id+1);
			H[K[id].x][i]=L[K[id].y][i]=B[K[id].z][i]=0,G[K[id].x][K[id].y]=0;
		}
}

int main()
{
	Read(),Sort();
	Ans=-1;
	Dfs(1),
	printf("%d\n",Ans);
	return 0;
}

另一种思路是按每一行空数的个数由小到大排序,依次枚举每一行,代码加上了位运算优化,具体见代码。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N=15;
int mp[N][N],H[N],L[N],G[N][N],K[N],sp[N],h[N],Ans;
/*
mp[][]记录矩阵的填数状态,如mp[i][j]记录第i行第j列填的数
H[]记录每一行的填的数的和
L[]记录每一列的填的数的和
G[x][y]记录第x行,第y列的填的数的和
通过H[],L[],G[][]我们就可以迅速计算出空格出可以填的数有哪些
h[]记录每一行得填数状态
通过h[]我们就可以迅速得出每一行的哪些位置的数还没有填
sp[]记录搜索顺序
*/

void Read()
{
	int YH;
	for(int i=1;i<=9;++i)
	{
		sp[i]=i;
		for(int j=1;j<=9;++j)
		{
			scanf("%d",&mp[i][j]);
			if(mp[i][j])
				h[i]|=1<<(j-1),YH=1<<(mp[i][j]-1),
				H[i]|=YH,L[j]|=YH,G[(i-1)/3][(j-1)/3]|=YH;
			else ++K[i];
		}
	}
}

bool Cmp(int a,int b) {return K[a]<K[b];}

int Point(int i,int j)
{
	if(i==1||i==9||j==1||j==9) return 6;
	if(i==2||i==8||j==2||j==8) return 7;
	if(i==3||i==7||j==3||j==7) return 8;
	if(i==4||i==6||j==4||j==6) return 9;
	return 10;
}

void Calc()
{
	int Res=0;
	for(int i=1;i<=9;++i)
		for(int j=1;j<=9;++j) Res+=mp[i][j]*Point(i,j);
	Ans=max(Ans,Res);
}

int Log(int x) {return x==1?1:Log(x>>1)+1;}

void Dfs(int id)
{
	if(id>9){Calc();return;}
	int i,j,x,y,Can;
	i=sp[id],x=(1<<9)-1-h[i],y=x&-x,h[i]|=y,j=Log(y),
	Can=(1<<9)-1-(H[i]|L[j]|G[(i-1)/3][(j-1)/3]);//能填的数
	while(Can)
	{
		int num=Can&-Can;
		Can-=num,mp[i][j]=Log(num),H[i]|=num,L[j]|=num,G[(i-1)/3][(j-1)/3]|=num;
		if(x==y) Dfs(id+1);//只有一个要填,填完跳至下一行
		else Dfs(id);
		H[i]-=num,L[j]-=num,G[(i-1)/3][(j-1)/3]-=num;
	}
	h[i]-=y;
}

int main()
{
	Read(),sort(sp+1,sp+10,Cmp),Dfs(1),printf("%d\n",Ans?Ans:-1);
	return 0;
}

总之这题的核心是优化搜索顺序

推荐靶形数独[搜索][位运算]

posted @ 2019-08-20 10:10  OItby  阅读(126)  评论(0编辑  收藏  举报