DFS(6):P1092 虫食算——复杂逻辑与剪枝

在这里插入图片描述

输出格式
一行,即唯一的那组解。

解是这样表示的:输出NN个数字,分别表示A,B,C,…A,B,C,…所代表的数字,相邻的两个数字用一个空格隔开,不能有多余的空格。

输入输出样例
输入 #1复制
5
ABCED
BDACE
EBBAA
输出 #1复制
1 0 3 4 2

参考以下DFS代码,可以摸索出一个DFS模板
在这里插入图片描述

void dfs(.....){
	if(符合返回要求){
	....
	return;
	}
	if(符合剪枝要求){
	.....
	return;
	}
	for(枚举相关状态){
		标记
		记录
		dfs(更深一层)
		擦除标记
		擦除记录
	}
}

总结目录

1 本题的枚举
2 相关的细节

1 本题的枚举

本题的枚举中,我们需要处理多个情况。我们要考虑的是上面的数取什么,下面的数字取什么,然后具体的搭配是否合理,并且不能取重复的数字。这个逻辑是符合人的思维的,但是具体的代码书写还是比较复杂的。

首先是dfs需要的信息,我一开始dfs只取了最后一列,但是发现情况太多,根本列举不完。后来看了参考的代码后,发现还应该列举行的信息。因此,我们应该在dfs中加入行的信息和列的信息,并且还需要加入进位的标记

那么有了这些标记,我们就要考虑如何进行深搜。首先是返回条件,比较简单,当列缩小到计算完毕,并且没有进位的时候就可以记录答案了,这个还是比较好写的。

其次是如何进行枚举的搜索。 我认为任何枚举,都是从数字开始枚举,然后利用数字去套我们的具体被枚举的物体。因此,对于最外层,一定是一个for循环枚举数字。当然,在这一题中,枚举当前的数字之前需要查看这个数字它是否已经被使用过了,如果已经有了,那么我们只要继续深搜就可以了。关键是如果这个数字没有,那么我们就要进行枚举了,也就是要进入for循环的枚举。

在这个问题中,本质其实是大的if判断是否要进入枚举,不枚举就可以深搜了。在进入枚举或者不枚举后,又分为要深搜更深一层还是跳跃到另外一个地方进行深搜。

2 剪枝相关的细节

本题中的剪枝,主要是使用的是判断是否符合要求,如果已经匹配出来的结果不符合了的话,那么继续计算下去是没有意义的,直接return就好了,也就实现了剪枝的处理。

代码

#include<iostream>
#include<cstring>
using namespace std;
int n;
int words[3][30];//记录下算式的值
int used[30];//由于每个字母有唯一值,因此利用hash进行查询是否已经被使用
int res[30];//用来枚举答案,0-25对应于A-Z即26个英文字母
int finalres[30];//用来记录最终答案


void dfs(int row,int col,int carry) {
	//多个枚举应该以枚举数字为基础,然后去推进层数
	if (col<0&&carry==0) {
		for (int i = 0; i < n; i++) {
			finalres[i] = res[i];
		}
		return;
	}

	for (int i = col; i >= 0; i--) {
		//这个循环的存在是为了剪掉那些已经枚举过的,但是在后面计算中不符合的情况
		int num1 = res[words[0][i]];
		int num2 = res[words[1][i]];
		int num3 = res[words[2][i]];
		if (num1 == -1 || num2 == -1 || num3 == -1) continue;//这里当时的逻辑是怎么想到用continue的	
		int sum = num1 + num2;
		if (sum%n != num3 && (sum + 1) % n != num3) return;//如果有一个不符合,那么一定是不合理的
	}

	if (res[words[row][col]] == -1) {
		//如果当前值是不存在的,也就是说这个值是没有被填充的,那么我们要枚举这个值
		for (int i = 0; i <n; i++) {
			if (!used[i]) {//某个数字没有被使用,那么可以在当前层枚举这个数字,在if中可以对当前层的业务逻辑进行if分类
				if (row != 2) {//如果没有枚举完毕,那么我们需要继续递增row的层数,直到3行全部出来才能计算
					used[i] = 1;
					res[words[row][col]] = i;
					dfs(row + 1, col, carry);
					used[i] = 0;
					res[words[row][col]] = -1;
				}
				else if (row == 2) {
					//这个分支仍然是要填充,只是填充的过程又加了剪枝(筛选),但是我觉得其实不用,因为前面已经剪掉了
					int sum = res[words[0][col]] + res[words[1][col]] + carry;
					if (sum%n != i)continue;
					used[i] = 1;
					res[words[row][col]] = i;
					dfs(0, col - 1, sum / n);
					used[i] = 0;
					res[words[row][col]] = -1;
				}
			}
		}
	}
	else if(res[words[row][col]]!=-1){
		//如果当前的值已经有了,那就不需要遍历了,直接根据层数进行更深的遍历
		if (row != 2) {
			dfs(row + 1, col, carry);
		}
		else if (row == 2) {
			int sum = res[words[0][col]] + res[words[1][col]] + carry;
			if (sum%n != res[words[row][col]])return;
				dfs(0, col - 1, sum / n);
		}
	}
}

void display() {
	for (int i = 0; i < n; i++) {
		if (i == 0) {
			cout << finalres[i];
		}
		else {
			cout << " " << finalres[i];
		}
	}
}

int main() {
	memset(res, -1, sizeof(res));//置为-1,说明没有值,不可置为0,因为0也算有效的
	cin >> n;
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < n; j++) {
			char ch;
			cin >> ch;
			words[i][j] = ch - 'A';//将字母转换为数字编号
		}
	}
	dfs(0, n - 1, 0);
	display();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值