深度搜索DFS——以洛谷P1036选数和P1605迷宫为例

迷宫

题目描述

给定一个 N × M N \times M N×M 方格的迷宫,迷宫里有 T T T 处障碍,障碍处不可通过。

在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。

给定起点坐标和终点坐标,每个方格最多经过一次,问有多少种从起点坐标到终点坐标的方案。

输入格式

第一行为三个正整数 N , M , T N,M,T N,M,T,分别表示迷宫的长宽和障碍总数。

第二行为四个正整数 S X , S Y , F X , F Y SX,SY,FX,FY SX,SY,FX,FY S X , S Y SX,SY SX,SY 代表起点坐标, F X , F Y FX,FY FX,FY 代表终点坐标。

接下来 T T T 行,每行两个正整数,表示障碍点的坐标。

输出格式

输出从起点坐标到终点坐标的方案总数。

样例 #1

样例输入 #1
2 2 1
1 1 2 2
1 2

样例输出 #1

1

提示

对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 5 1 \le N,M \le 5 1N,M5 1 ≤ T ≤ 10 1 \le T \le 10 1T10 1 ≤ S X , F X ≤ n 1 \le SX,FX \le n 1SX,FXn 1 ≤ S Y , F Y ≤ m 1 \le SY,FY \le m 1SY,FYm

个人理解

这题是标准的深度搜索,走迷宫,形象又生动地把深度搜索的过程展现了出来。在洛谷题解中看到了一个深搜模板,感觉非常精炼。

下面是深搜模板

void dfs(int t){    //t为递归的层数或是用于判定的结果数
	if(结束条件){
		计数或是无操作;
		return;     //跳出递归,继续调用栈的上一层递归
	}
	else{           //搜索没有结束,继续进行搜索
		for(int i=1;i<=尝试方法数;i++){//考虑当前可走的所有情况
			if(满足进一步搜索的条件){
				对当前下标为i的地方进行标记;
				dfs(t+1);//对标记的地方进行搜索,并在此基础上继续搜索
				去掉标记; //回溯一步
		}
	}
}

迷宫题解

在迷宫这题中,可以直接套用深搜模板进行搜索,如下:

void dfs(int x, int y){    //表示此时正在搜索的迷宫坐标
	if(现在的坐标为终点坐标){
		计数++;
		return;     //跳出递归,继续调用栈的上一层递归
	}
	else{           //搜索没有结束,继续进行搜索
		for(int i=1;i<=4;i++){//一共仅可向上下左右四个方向走
			if(下一步未出界&&没走过&&不是障碍物){
				对下一步坐标进行标记;
				dfs(xx,yy);//对标记的地方进行搜索,并在此基础上继续搜索
				去掉标记; //回溯一步
		}
	}
}

具体实现如下:

#include<bits/stdc++.h>
using namespace std;
int N,M,T,SX,SY,FX,FY;
bool alr[6][6]={}; //记录是否走过的数组
int sum=0;         //记录路的条数
int dx[4]={0,1,0,-1};//数组dx和dy共同构成上下左右4个方向
int dy[4]={-1,0,1,0};
bool mape[6][6]={};//地图本身

void dfs(int x,int y){
	if(x==FX&&y==FY){
		sum++;
		return;
	}
	else{
		for(int i=0;i<4;i++){
			int xx=x+dx[i];
			int yy=y+dy[i];
			if(xx>0&&xx<=M&&yy>0&&yy<=N&&alr[xx][yy]==0&&mape[xx][yy]==0){
				alr[xx][yy]=1;
				dfs(xx,yy);
				alr[xx][yy]=0;
			}
		}
	}
}

int main(){
	cin>>M>>N>>T;
	cin>>SX>>SY>>FX>>FY;
	for(int i=0;i<T;i++){
		int x,y;
		cin>>x>>y;
		mape[x][y]=1;
	}
	alr[SX][SY]=1;
	dfs(SX,SY);
	cout<<sum;
	return 0;
}

这样就完成了对迷宫的深度搜索,并且能够保证不重复地计数通关迷宫共有几条路可走。
到这里,我当时以为我已经理解了深度搜索,直到我看到了P1036选数。

[NOIP2002 普及组] 选数

题目描述

已知 n n n 个整数 x 1 , x 2 , ⋯   , x n x_1,x_2,\cdots,x_n x1,x2,,xn,以及 1 1 1 个整数 k k k k < n k<n k<n)。从 n n n 个整数中任选 k k k 个整数相加,可分别得到一系列的和。例如当 n = 4 n=4 n=4 k = 3 k=3 k=3 4 4 4 个整数分别为 3 , 7 , 12 , 19 3,7,12,19 3,7,12,19 时,可得全部的组合与它们的和为:

3 + 7 + 12 = 22 3+7+12=22 3+7+12=22

3 + 7 + 19 = 29 3+7+19=29 3+7+19=29

7 + 12 + 19 = 38 7+12+19=38 7+12+19=38

3 + 12 + 19 = 34 3+12+19=34 3+12+19=34

现在,要求你计算出和为素数共有多少种。

例如上例,只有一种的和为素数: 3 + 7 + 19 = 29 3+7+19=29 3+7+19=29

输入格式

第一行两个空格隔开的整数 n , k n,k n,k 1 ≤ n ≤ 20 1 \le n \le 20 1n20 k < n k<n k<n)。

第二行 n n n 个整数,分别为 x 1 , x 2 , ⋯   , x n x_1,x_2,\cdots,x_n x1,x2,,xn 1 ≤ x i ≤ 5 × 1 0 6 1 \le x_i \le 5\times 10^6 1xi5×106)。

输出格式

输出一个整数,表示种类数。

样例 #1

样例输入 #1
4 3
3 7 12 19

样例输出 #1

1

选数的个人理解

由于选数所需要的是在 n n n个数中选出 m m m个数( m ≤ n m \le n mn),其实就是枚举,出于对不重复不遗漏枚举的需要,我们需要对 n n n中的 m m m个数进行按顺序的搜索,而深度搜索之所以适用于选数这道题,在我的个人理解看来,与递归函数实际工作时所使用的递归工作栈有关。

以样例为例,样例要求我们在 4 4 4个数中选 3 3 3个数,对 4 4 4个数分别编号为 1 , 2 , 3 , 4 1,2,3,4 1234。按字典序进行枚举,即为:

					1,2,3
					1,2,4
					1,3,4
					2,3,4

先从下标最小的数开始,固定前两位,按顺序搜索填补第三位的位置,如 1 , 2 , 3 、 1 , 2 , 4 1,2,3、1,2,4 123124。然后再固定第一位,顺序搜索并固定第二位,再顺序搜索填补第三位的位置,如 1 , 3 , 4 1,3,4 134。以此类推,为了不重复,需要暂时将前面的结果暂时固定,因此需要栈这种先入后出的性质,下面演示深度搜索用于选数的递归调用栈。

首先是深度搜索的代码:

void dfs(int t,int sum,int index){ //t为当前共选中的数的个数,sum为它们的和,index为当前需要搜索并暂时固定的数字下标
	if(t==k){					//如果数字选够了
		if(isprime(sum))		//判断是否是质数
			ans++;				//如果是,则总和加上1
			return;
	}
	else{
		for(int i=index;i<n;i++){ //从当前所固定的数字开始固定
			dfs(t+1,sum+x[i],i+1); //进行下一层递归
		}
	}
}

其次,以样例为例进行运行过程中递归调用栈的演示。

1.dfs(0,0,0) for循环,i=0
2.dfs(1,3,1)   此时固定了第一位为[0]
1.dfs(0,0,0)
4.dfs(3,22,3)   i=3  [0][1][2]    由3中的for循环调用
3.dfs(2,10,2)   i=2  [0][1]       由2中的for循环调用
2.dfs(1,3,1)    i=1  固定了[0]     由1中的for循环调用
1.dfs(0,0,0)    i=0

此时,在 d f s ( 3 , 22 , 3 ) dfs(3,22,3) dfs(3,22,3)中,由于选数个数为3满足跳出递归条件,因此弹出该条记录,并判断22是否为质数,继续下一个递归。
由于第4条已经弹出栈,因此继续第3条: d f s ( 2 , 10 , 2 ) dfs(2,10,2) dfs(2102)的for循环,此时i++,为3,因此第4条入栈的dfs其实搜索的第三个位置由原来的[2]变为[3]:

4.dfs(3,29,4)   i=4  [0][1][3]    由3中的for循环调用
3.dfs(2,10,2)   i=3  [0][1]       由2中的for循环调用
2.dfs(1,3,1)    i=1  固定了[0]     由1中的for循环调用
1.dfs(0,0,0)    i=0

新入栈的第4条也满足弹出条件,弹出并判断,继续3中的for循环,for循环在i=3时结束了,因此第3条记录也运行结束,将第3条也弹出栈。目前栈中只剩1,2两条记录。

2.dfs(1,3,1)    i=1  固定了[0]     由1中的for循环调用
1.dfs(0,0,0)    i=0

继续第2条的for循环运行,i++,此时i=2。实则是开始变动并暂时固定第二位,然后在第二位入栈的基础上,再次重复刚才的搜索第三位的操作。

事实上,只有当最后一位可行的数全部搜索完毕后,才能弹出栈,对前一位进行更改,这样其实就做到了不重复不遗漏的搜索。

3.dfs(2,15,3)   i=3 [0][2]
2.dfs(1,3,1)    i=2  固定了[0]     由1中的for循环调用
1.dfs(0,0,0)    i=0
4.dfs(3,34,4)   i=4 [0][2][3]
3.dfs(2,15,3)   i=3 [0][2]
2.dfs(1,3,1)    i=3  固定了[0]     由1中的for循环调用
1.dfs(0,0,0)    i=0
4.dfs(3,38,4)   i=4  [1][2][3]
3.dfs(2,19,3)   i=3  [1][2]
2.dfs(1,7,2)    i=2  固定了[1]
1.dfs(0,0,0)    i=1

接下来2,3,4全部弹出,由于第一位再往后就不能凑够3个数了,便不再展示了。

换而言之,将需要搜索的下标用树形结构展示更易于理解。需要进行深度搜索的其实是这样一棵树:
深度搜索树

对这棵树及其子树进行深度搜搜,每次搜索至深度为所需深度即可停止。

对于洛谷上的p1036目,我们可以使用Python来解决。下面是一个可能的解法: ```python def dfs(nums, target, selected_nums, index, k, sum): if k == 0 and sum == target: return 1 if index >= len(nums) or k <= 0 or sum > target: return 0 count = 0 for i in range(index, len(nums)): count += dfs(nums, target, selected_nums + [nums[i]], i + 1, k - 1, sum + nums[i]) return count if __name__ == "__main__": n, k = map(int, input().split()) nums = list(map(int, input().split())) target = int(input()) print(dfs(nums, target, [], 0, k, 0)) ``` 在这个解法中,我们使用了深度优先搜索DFS)来找到满足要求的数列。通过递归的方式,我们遍历了所有可能的数字组合,并统计满足条件的个数。 首先,我们从给定的n和k分别表示数字个数和需要选取的数字个数。然后,我们输入n个数字,并将它们存储在一个列表nums中。接下来,我们输入目标值target。 在dfs函数中,我们通过迭代index来选择数字,并更新选取的数字个数k和当前总和sum。如果k等于0且sum等于target,我们就找到了一个满足条件的组合,返回1。如果index超出了列表长度或者k小于等于0或者sum大于target,说明当前组合不满足要求,返回0。 在循环中,我们不断递归调用dfs函数,将选取的数字添加到selected_nums中,并将index和k更新为下一轮递归所需的值。最终,我们返回所有满足条件的组合个数。 最后,我们在主程序中读入输入,并调用dfs函数,并输出结果。 这是一种可能的解法,但不一定是最优解。你可以根据目要求和测试数据进行调试和优化。希望能对你有所帮助!
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值