【习题·搜索】[NOIP2009]靶型数独(搜索+剪枝+位运算优化)

题目

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

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

【习题·搜索】[NOIP2009]靶型数独(搜索+剪枝+位运算优化)_第1张图片

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

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

【习题·搜索】[NOIP2009]靶型数独(搜索+剪枝+位运算优化)_第2张图片

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

题目大意

在9*9的矩阵内,每一个点都有一个分数,你先在需要求出一个数独使得数独上的每一个点乘这个点的分数之和最大,并输出这一个分数。

做法

这一道题目的难点就是求解数独的过程,我们需要对整一个搜索树进行剪枝。

如果我们需要对整一个问题的时间复杂度进行优化,我们需要:

  • 在每一次搜索的时候不以点的顺序进行搜索,而是找到可以选择点数最小的格子进行搜索,这样整一棵搜索树的的规模就变小了;因此搜索的参数就是剩下的点数而不是当前的坐标,然后我们暴力找到可以选出的最小点(常数 = 81 可以忽略)。

然后就需要进行常数优化惹:

  • 显然每一个状态如果用数组标记和调用都过于浪费时间,我们可以用27个9位二进制数来分别标记行、列和九宫格的状态。如果需要找到当前可以用的数字,可以使用lowbit运算(返回末尾连续的0和第一个1的数值),然后需处理一下即可知道数字,暴力标记即可。

现在回到找最小点这一个问题上:

  • 我们可以枚举每一个点,通过二进制,得到对应的1的位置;而每一个二进制数有多少个1则需要通过预处理解决,在一个数的基础上不断减lowbit即可;而每一个位置可以填的条件就是行、列、九宫格做与运算的结果,如果当前值是0说明不合法直接退出即可。
#include 
using namespace std;
bool flag;
int x[200];
int y[200];
int n[200];
int tot[300];
int num[1000000];
char s[300][300];
int nine[100][100]=
{
	{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}
};
void print(void)
{
	for (int i=1;i<=9;++i)
	{
		for (int j=1;j<=9;++j)
		    cout<<s[i][j];
	}
	puts("");
}
void change(int i,int j,int v)
{
	x[i]^=(1<<v-1);
	y[j]^=(1<<v-1);
	n[nine[i][j]]^=(1<<v-1);
}
bool dfs(int D)
{
	if (D==0) return 1;
	int Min=INT_MAX,xx=0,yy=0,temp;
	for (int i=1;i<=9;++i)
	    for (int j=1;j<=9;++j)
	        if (s[i][j]=='.')
	        {
	        	int now=x[i]&y[j]&n[nine[i][j]];
	        	if (now==0) return 0;
	        	if (tot[now]<Min)
	        	{
	        		Min=tot[now];
	        		xx=i;
	        		yy=j;
	        		temp=now;
	        	}
	        }
	for (int i=temp;i;i-=(i&-i))
	{
		int low=i&-i;
		int k=num[low];
		change(xx,yy,k);
		s[xx][yy]=k+'0';
		if (dfs(D-1)) return 1;
		change(xx,yy,k);
		s[xx][yy]='.';
	}
	return 0;
} 
int main(void)
{
	freopen("b.in","r",stdin);
	freopen("b.out","w",stdout);
	for (int i=0;i<(1<<10);++i)
	    for (int j=i;j;j-=(j&-j))
	        tot[i]++;
	for (int i=1;i<10;++i) 
		num[1<<i-1]=i; 
	//累计每一个数lowbit值中的1的个数 
	cin>>s[1][1];
	up:
	flag=0;
	for (int i=1;i<=9;++i)
	    for (int j=(i==1?2:1);j<=9;++j)
		    cin>>s[i][j];
	for (int i=1;i<=9;++i)
	    x[i]=y[i]=n[i]=(1<<9)-1;
	//初始化:将每一个二进制位改成全1 
	int sum=0;
	for (int i=1;i<=9;++i)
	    for (int j=1;j<=9;++j)
		    if (s[i][j]!='.')
		    {
		    	int v=s[i][j]-'0';
		    	change(i,j,v);
		    }
		    else sum++;
	dfs(sum);
	print();
	cin>>s[1][1];
	if (s[1][1]!='e') goto up;
	return 0;
}

你可能感兴趣的:(搜索,[算法进阶指南]习题题解)