数独——拥有一定难度的回溯练习题,值得一看

本文介绍了如何使用C++实现数独游戏的解决,利用回溯算法解决了数独难题。文章详细讲述了数独规则,展示了从简单到复杂(如最难数独)的解题过程,并分享了处理数独问题的技巧和代码实例。
摘要由CSDN通过智能技术生成

数独相信大家都玩过,也都拥有不同的策略,那么放到C++中又是怎样的呢?其实它就是回溯算法。话不多说,直接用例题来讲解:

Description

数独是根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫内的数字均含1−9且不重复。每一道合格的数独谜题都有且仅有唯一答案,推理方法也以此为基础,任何无解或多解的题目都是不合格的。

芬兰一位数学家号称设计出全球最难的“数独游戏”,并刊登在报纸上,让大家去挑战。

这位数学家说,他相信只有“智慧最顶尖”的人才有可能破解这个“数独之谜”。

据介绍,目前数独游戏的难度的等级有一到五级,一是入门等级,五则比较难。不过这位数学家说,他所设计的数独游戏难度等级是十一,可以说是所以数独游戏中,难度最高的等级。他还表示,他目前还没遇到解不出来的数独游戏,因此他认为“最具挑战性”的数独游戏并没有出现。

Format

Input

一个未填的数独。

Output

填好的数独。

这道题是洛谷上的原题数独 - 洛谷

看着题很长,其实一点也不难,只需要一个点一个点的枚举加上回溯就可以了,再开三个数组来记录当前这一行和这一列以及再一个3*3的格子里的数字的剩余形况。

ACcode

 

#include<bits/stdc++.h>
using namespace std;

int h[10][10],l[10][10],a[10][10],ans[10][10],g[5][5][10],cmp;

void dfs(int x,int y){
	if (cmp==1)return;
	if (x==10&&y==1){
		for (int i=1;i<=9;i++){
			for (int j=1;j<=9;j++){
				cout<<ans[i][j]<<" ";
			}
			cout<<endl;
		}
		cmp=1;
		return;
	}
	
	if (a[x][y]!=0){
		ans[x][y]=a[x][y];
		if (y==9){
			dfs(x+1,1);
		}else {
			dfs(x,y+1);
		}
		return;
	}else {
		for (int i=1;i<=9;i++){
			if (h[x][i]==0&&l[y][i]==0&&g[(x-1)/3+1][(y-1)/3+1][i]==0){
				h[x][i]=1;
				l[y][i]=1;
				g[(x-1)/3+1][(y-1)/3+1][i]=1;
				ans[x][y]=i;
				//
				if (y==9){
					dfs(x+1,1);
				}else {
					dfs(x,y+1);
				}
				//
				h[x][i]=0;
				l[y][i]=0;
				g[(x-1)/3+1][(y-1)/3+1][i]=0;
			}
		}
	}

	return;
}
int main(){
	for (int i=1;i<=9;i++){
		for (int j=1;j<=9;j++){
			cin>>a[i][j];
			if (a[i][j]!=0){
				h[i][a[i][j]]=1;
				l[j][a[i][j]]=1;
				g[(i-1)/3+1][(j-1)/3+1][a[i][j]]=1;
			}
		}
	}
	dfs(1,1);
	return 0;
}

这道题会了的话我们来看一个有一点变形的

Description

题目背景

数独是指满足如下条件的 9×9矩阵:

  • 每一行、每一列都包含从 1 至 9 不重不漏的数字;
  • 将其划分为 9 个 3×3 的小矩阵,每个小矩阵也都包含从 1 至 9 不重不漏的数字。

题目描述

给出一个 9×9 的矩阵,里面填了一些 1 至 9 之间的整数,将其补全为一个完整的数独并输出。未填数的格子以 0 表示。

输入格式

从标准输入读入数据。

输入共 9 行,每行包含 9 个整数 aij​(0≤aij​≤9),整数之间不含空格,代表待补全的数独。

输出格式

输出到标准输出。

如果数独有解,输出共 9 行,每行包含 9 个整数 aij​(1≤aij​≤9),整数之间不含空格,代表填好后的数独。

如果有多于一个解,任意输出一个解即可。

如果数独无解,输出 0 。

这道题其实就只需要再判断完之后输出一个0就可以了,非常的easy

#include<bits/stdc++.h>
using namespace std;

int h[10][10],l[10][10],a[10][10],ans[10][10],g[5][5][10],cmp;

void dfs(int x,int y){
	int flag=1;
	
	if (cmp==1)return;
	if (x==10&&y==1){
		for (int i=1;i<=9;i++){
			for (int j=1;j<=9;j++){
				cout<<ans[i][j];
			}
			cout<<endl;
		}
		cmp=1;
		exit(0);
	}
	
	if (a[x][y]!=0){
		ans[x][y]=a[x][y];
		if (y==9){
			dfs(x+1,1);
		}else {
			dfs(x,y+1);
		}
		return;
	}else {
		for (int i=1;i<=9;i++){
			if (h[x][i]==0&&l[y][i]==0&&g[(x-1)/3+1][(y-1)/3+1][i]==0){
				flag=0;
				//
				h[x][i]=1;
				l[y][i]=1;
				g[(x-1)/3+1][(y-1)/3+1][i]=1;
				ans[x][y]=i;
				//
				if (y==9){
					dfs(x+1,1);
				}else {
					dfs(x,y+1);
				}
				//
				h[x][i]=0;
				l[y][i]=0;
				g[(x-1)/3+1][(y-1)/3+1][i]=0;
			}
		}
	}
	return;
}
int main(){
	string s;
	for (int i=1;i<=9;i++){
		cin>>s;
		for (int j=1;j<=9;j++){
			a[i][j]=s[j-1]-'0';
			if (a[i][j]!=0){
				h[i][a[i][j]]=1;
				l[j][a[i][j]]=1;
				g[(i-1)/3+1][(j-1)/3+1][a[i][j]]=1;
			}
		} 
	}
	dfs(1,1);
    cout<<0;
	return 0;
}

最后我们看一道[NOIP2009 提高组] 靶形数独 - 洛谷——提高+/省选-

题目背景

此为远古题,不保证存在可以通过任意符合要求的输入数据的程序

题目描述

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

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

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

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

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

输入格式

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

输出格式

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

首先这道题如果用上面的方法来写的话,只能拿60分,其实这个分数在赛场上是比较可以了的,但是嘛,我们是学习,自然要弄懂才行,所以我们来具体分析分析:

首先我们知道在填数独的时候,一般情况下会去填这一行或这一列已经有比较多的数字了的地方,那么我们可不可以利用这个方法,找到每一行所剩下的位置有几个,然后排一个序,从小往大的去找呢?这个思路就非常非常的好了,可以拿整整100分!

ACcode

#include <bits/stdc++.h>
using namespace std;

inline int read(){
	int x=0;
	char ch=0;
	while (ch<'0'||ch>'9')ch=getchar();
	while (ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
	return x;
}//用了一下快读,可以提速
int h[10][10],l[10][10],a[10][10],ans[10][10],g[5][5][10],maxn=-1,cnt=1;

int fen[10][10]={{0,0,0,0,0,0,0,0,0,0},{0,6,6,6,6,6,6,6,6,6},{0,6,7,7,7,7,7,7,7,6},{0,6,7,8,8,8,8,8,7,6},{0,6,7,8,9,9,9,8,7,6},{0,6,7,8,9,10,9,8,7,6},{0,6,7,8,9,9,9,8,7,6},{0,6,7,8,8,8,8,8,7,6},{0,6,7,7,7,7,7,7,7,6},{0,6,6,6,6,6,6,6,6,6}};//手动打表

struct f{
    int num=9;
    int hang;
}sx[10];

bool cmp(f a1,f a2){
    return a1.num<a2.num;
}

void dfs(int x,int y){
	if (cnt==10){
		int cnt=0;
		for (int i=1;i<=9;i++){
			for (int j=1;j<=9;j++){
				cnt+=ans[i][j]*fen[i][j];
			}
		}
		maxn=max(maxn,cnt);
		return;
	}
	
	if (a[x][y]!=0){
		ans[x][y]=a[x][y];
		if (y==9){
            cnt++;
			dfs(sx[cnt].hang,1);
            cnt--;
		}else {
			dfs(x,y+1);
		}
		return;
	}else {
		for (int i=1;i<=9;i++){
			if (h[x][i]==0&&l[y][i]==0&&g[(x-1)/3+1][(y-1)/3+1][i]==0){
				h[x][i]=1;
				l[y][i]=1;
				g[(x-1)/3+1][(y-1)/3+1][i]=1;
				ans[x][y]=i;
				//
				if (y==9){
                    cnt++;
					dfs(sx[cnt].hang,1);
                    cnt--;//这个也要回溯,当初我就是在这里卡了好久
				}else {
					dfs(x,y+1);
				}
				//
				h[x][i]=0;
				l[y][i]=0;
				g[(x-1)/3+1][(y-1)/3+1][i]=0;
			}
		}
	}

	return;
}

int main(){
	for (int i=1;i<=9;i++){
        sx[i].hang=i;//存下行的位置
		for (int j=1;j<=9;j++){
			a[i][j]=read();
			if (a[i][j]!=0){
				h[i][a[i][j]]=1;
				l[j][a[i][j]]=1;
				g[(i-1)/3+1][(j-1)/3+1][a[i][j]]=1;
                sx[i].num--;
			}
		}
	}
    sort(sx+1,sx+10,cmp);
	dfs(sx[cnt].hang,1);
	cout<<maxn;
	return 0;
}

 

  看了这么久,作者也写了这么久,能不能点一个赞,在收藏一下呢?最好的话在点个关注吧

谢谢啦!

  • 8
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值