记忆化搜索(4):P1074 靶形数独——有优先顺序的dfs

本文探讨了P1074靶形数独问题,介绍了一种有优先顺序的深度优先搜索(DFS)方法来解决数独问题,通过优先搜索数字较少的行来加速求解过程。文章详细解释了搜索流程、细节及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

P1074 靶形数独

在这里插入图片描述

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

总结目录

1 本题搜索思路与加速方法——有优先顺序的dfs
2 具体搜索流程
3 本题搜索细节

1 有优先顺序的dfs

本题其实有点类似于八皇后问题的变种。只不过增加了一个九宫格的blockmark[][]的记录,rowmark[][]和colmark[][]应该都是已知的。那么深搜的时候从第一个点进行搜索,搜到最后一个点得到sum,这个算是暴力搜索的方法了,此时的dfs(int row,int col),返回条件是row==10,也就是搜到10的时候说明搜完了。

但是这样暴力搜索的话存在一个问题,就是一开始可能会有很多0,这样会展开非常多的分支,由于每次都要搜索到底部,因此是很费时的。所以我们会考虑,如何去进行更加有效率的搜索。实际上在日常中,我们做数独往往都是从数字多的地方进行填,这样可以提高我们的效率,减少无效的枚举。 虽然我暂时还无法从理论上证明为什么这么做可以加速,但是这就是本题的加速方法。

也就是说我们在深搜的时候,根据已知条件(本题中是每一行0的多少,0少的值得深搜),给出一个深搜的权重,我们会去优先搜索这些点,以此来进行加速。 也就是我们给出了一个深搜的加速路径,这样可以提高搜索的效率。

2 具体搜索的流程

如果给定一个搜索路径,那么我们要先把路径给确定下来。本题中我使用了Point结构体,然后根据每一行的0的多少进行填写搜索的路径。填写完之后,进行具体的搜索。这样做的原因是,如果使用dfs(int row,int col)的话,具体dfs的时候,在程序中可能不知道下一个要dfs的地方是那里,这里先确定了路径,然后按照点来进行深搜会更好。

另外,对于权重的选取,在输入数据的时候就统计了每一行的0的个数,然后使用sort函数进行统计,自己写了比较器。然后在main中将路径写进points[]数组中。

深搜的过程到达尽头更新flag和maxres就可以了,使用的回溯算法也没有什么好说的。

3 本题搜索细节

本题一开始我只拿了85分,因我我在深搜中判断并mark已经填的数。但是这些数应该是在读入数据的时候就已经在rowmark[][],colmark[][],blockmark[][]里面记录好了。如果在dfs搜索里面才mark,那么有可能会出现一开始就是0,然后对0枚举了1,但是同一行后面已经有1这样的情况。这样的话就会出错,出现同一行出现2个1的情况。

因此必须在预处理阶段就进行mark,我在这里可能也是受到了虫食算那题的影响。

代码

#include<iostream>
#include<algorithm>
using namespace std;
#define maxsize 15

struct node{
	int rowindex;
	int zerocnt;
};
int mat[maxsize][maxsize];//存储初始矩阵的值
int rowmark[maxsize][maxsize];//rowrark[row][i]==1表示第row行,数字i已经被使用过
int colmark[maxsize][maxsize];//colmark[col][i]==1表示第col行,数字i已经被使用过
int blockmark[maxsize][maxsize];//nineblock[ind][i]==1表示第ind个九宫,数字i已经被使用过
int maxres = 0;//结果
bool flag = 0;//是否存在满足的答案
node dfsrow[maxsize];//记录了行的搜索顺序,dfsrow[1]表示第一个搜索的行的0是最少的

struct Point {
	int row;
	int col;
};
Point points[100];

bool cmp(node a, node b) {
	return a.zerocnt < b.zerocnt;
}


int loc2blkind(int row, int col) {
	//输入[row,col],返回对应的block下标
	if (row <= 3) {
		if (col <= 3)return 1;
		if (col > 3 && col <= 6) return 2;
		if (col > 6 && col <= 9) return 3;
	}
	if (row > 3 && row <= 6) {
		if (col <= 3)return 4;
		if (col > 3 && col <= 6)return 5;
		if (col > 6 && col <= 9)return 6;
	}
	if (row > 6 && row <= 9) {
		if (col <= 3)return 7;
		if (col > 3 && col <= 6)return 8;
		if (col > 6 && col <= 9)return 9;
	}
}

//本题剪枝要点,从0少的地方进行深搜比随机乱搜更快
void getdata() {
	for (int row = 1; row <= 9; row++) {
		dfsrow[row].rowindex = row;
		dfsrow[row].zerocnt = 0;
		for (int col = 1; col <= 9; col++) {
			cin >> mat[row][col];
			if (mat[row][col] != 0) {
				int num = mat[row][col];
				int blkind = loc2blkind(row, col);
				rowmark[row][num] = 1;
				colmark[col][num] = 1;
				blockmark[blkind][num] = 1;//如果当前的统计点不为0,则打上标记
				//这里一定要注意,此处的标记一定是在输入的过程中就打上标记,也就是
				//一开始就打上标记,否则在搜索过程中,如果一开始就是0,那么可能会出现
				//同一行存在2个1,或者多个1的情况,导致出错!
			}
			if (mat[row][col] == 0) {
				dfsrow[row].zerocnt = dfsrow[row].zerocnt + 1;
			}
		}
	}
	sort(dfsrow + 1, dfsrow + 10, cmp);
}


int calsum(int row, int col) {
	//参考大佬的代码,发现从外面往里面剥代码会很简单
	if (row == 1 || row == 9 || col == 1 || col == 9)return mat[row][col] * 6;
	if (row == 2 || row == 8 || col == 2 || col == 8)return mat[row][col] * 7;
	if (row == 3 || row == 7 || col == 3 || col == 7)return mat[row][col] * 8;
	if (row == 4 || row == 6 || col == 4 || col == 6)return mat[row][col] * 9;
	else return mat[row][col]*10;
}

void dfs(int pcnt,int cursum) {
	//pcnt表示正在统计第pcnt个点
	if (pcnt == 82) {
		maxres = max(maxres, cursum);
		flag = 1;
		return;
	}

	Point curpoint = points[pcnt];//当前要统计的点
	int row = curpoint.row;
	int col = curpoint.col;
	int blkind = loc2blkind(row, col);

	if (mat[row][col] != 0) {//如果当前的统计点不为0,则打上标记,求和之后继续深搜
		int addval = calsum(row, col);
		dfs(pcnt + 1, cursum + addval);
	}
	else if (mat[row][col] == 0) {//如果当前的统计点为0,那么就需要枚举
		for (int i = 1; i <= 9; i++) {//枚举数字
			if (rowmark[row][i] != 1 && colmark[col][i] != 1 && blockmark[blkind][i] != 1) {

				rowmark[row][i] = 1;
				colmark[col][i] = 1;
				blockmark[blkind][i] = 1;
				mat[row][col] = i;//标记

				int addval = calsum(row, col);
				dfs(pcnt + 1, cursum + addval);//这里尽量用加法

				rowmark[row][i] = 0;
				colmark[col][i] = 0;
				blockmark[blkind][i] = 0;
				mat[row][col] = 0;//擦除标记,回溯
			}
		}
	}
}


int main() {
	getdata();
	//计算点的顺序
	int cnt = 1;
	for (int row = 1; row <= 9; row++) {
		for (int col = 1; col <= 9; col++) {
			points[cnt].row = dfsrow[row].rowindex;
			points[cnt].col = col;
			cnt++;
		}
	}

	dfs(1,0);

	if (flag == 0) {
		cout << "-1" << endl;
	}
	else if (flag != 0) {
		cout << maxres << endl;
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值