【杀红眼了】dfs刷题 (洛谷P1683 入门,P1596Lake Counting S,P1025数的划分,洛谷P1088火星人,Acwing1114. 棋盘问题)

dfs刷题,一次刷个够~    8000字预警~

刷题之前 先总结一下所谓的dfs模板

void/int dfs(int x)//参数的个数和返回值要根据题目确定
	{
		if(剪枝) return;
		if(结束递归的条件) {
			if(满足题目限定条件)  
				记录/输出
		}
		return;
		for(搭建递归树层//里面也可剪枝)
		{
			if(剪枝) continue;
			dfs(x+1)//进入下一层递归
			回溯
		}
		
	}

dfs(深度优先搜索) 核心思想:一条路走到黑

一般分为指数递归,排列递归,组合递归等,递归的代码也不完全相同。

//注意 本文乃是刷题课,可能难度比较大,新入门dfs的同学可以看博主第二篇文章的例题,那个比较简单

先练练手

入门 - 洛谷

不是任何人都可以进入桃花岛的,黄药师最讨厌像郭靖一样呆头呆脑的人。所以,他在桃花岛的唯一入口处修了一条小路,这条小路全部用正方形瓷砖铺设而成。有的瓷砖可以踩,我们认为是安全的,而有的瓷砖一踩上去就会有喷出要命的毒气,那你就死翘翘了,我们认为是不安全的。你只能从一块安全的瓷砖上走到与他相邻的四块瓷砖中的任何一个上,但它也必须是安全的才行。

由于你是黄蓉的朋友,她事先告诉你哪些砖是安全的、哪些砖是不安全的,并且她会指引你飞到第一块砖上(第一块砖可能在任意安全位置),现在她告诉你进入桃花岛的秘密就是:如果你能走过最多的瓷砖并且没有死,那么桃花岛的大门就会自动打开了,你就可以从当前位置直接飞进大门了。

注意:瓷砖可以重复走过,但不能重复计数。

输入格式

第一行两个正整数 W 和 H,分别表示小路的宽度和长度。

以下 H 行为一个H×W 的字符矩阵。每一个字符代表一块瓷砖。其中,. 代表安全的砖,# 代表不安全的砖,@ 代表第一块砖。

输出格式

输出一行,只包括一个数,即你从第一块砖开始所能安全走过的最多的砖块个数(包括第一块砖)。

输入输出样例

输入 #1复制

11 9
.#.........
.#.#######.
.#.#.....#.
.#.#.###.#.
.#.#..@#.#.
.#.#####.#.
.#.......#.
.#########.
...........

输出 #1复制

59

说明/提示

数据规模与约定

对于全部的测试点,保证 1≤W,H≤20。

解题

迷宫问题  我们有上下左右四个方向可以走 

1.怎么用代码来表示走一步呢?

只需要两个数组代表方向

直观地理解一下  

所以我们只需要用原来的坐标加上走一步的坐标,就是下一步的坐标辣

用一个4次的for循环就可以代表可以试4个方向 

2.怎么防止重复记录?

可以用一个数组记录,走过就 计true  并且计数器+1。下一次遇到走过的格子,直接换个方向走

而且不需要回溯,不然会重复记录。

判断边界不能走,.不能走

package dfs;

import java.util.Scanner;

//1683 1596 1114 1025 
public class 入门 {
	static final int N=21;
static char [][] map=new char [N][N];
static boolean [][] book=new boolean [N][N];
 static int n;
 static int m;
 static int dx[]= {-1,0,1,0};//控制上右下左
 static int dy[]= {0,1,0,-1};
 static int cnt=0;
 public static void main(String[] args) {
	Scanner sc=new Scanner(System.in);
	m=sc.nextInt();
	n=sc.nextInt();
	String s;
	sc.nextLine();//读取回车
	for(int i=0;i<n;i++)//读取地图
	{
		s=sc.nextLine();
		map[i]=s.toCharArray();
		
	}
	
	for(int i=0;i<n;i++)//读取地图
	{
		for(int j=0;j<m;j++)
		{
			if(map[i][j]=='@') 
			{
				book[i][j]=true;
				dfs(i,j);}
		}
	}
	cnt++;//算上起点
	System.out.println(cnt);
}

 static void dfs(int x,int y)

 { 
	 for(int i=0;i<4;i++)
 { 
	int a=x+dx[i];
	int b=y+dy[i];//走一步
	//记录走过了
	
	if(a<0||a>=n||b<0||b>=m) continue;//边界返回
	
	if(map[a][b]!='.') continue;//不安全
	
	if(book[a][b]) continue;//走过了
	
	     cnt++;//如果可以走并且没走过 就计数
		 book[a][b]=true; //标记为走过
	  dfs(a,b);
 }
	 
 }
}
 

有点蒙,没事再来一道

[USACO10OCT] Lake Counting S - 洛谷

P1596 [USACO10OCT] Lake Counting S

Due to recent rains, water has pooled in various places in Farmer John's field, which is represented by a rectangle of N x M (1 <= N <= 100; 1 <= M <= 100) squares. Each square contains either water ('W') or dry land ('.'). Farmer John would like to figure out how many ponds have formed in his field. A pond is a connected set of squares with water in them, where a square is considered adjacent to all eight of its neighbors. Given a diagram of Farmer John's field, determine how many ponds he has.

输入格式

Line 1: Two space-separated integers: N and M * Lines 2..N+1: M characters per line representing one row of Farmer John's field. Each character is either 'W' or '.'. The characters do not have spaces between them.

输出格式

Line 1: The number of ponds in Farmer John's field.

题意翻译

由于近期的降雨,雨水汇集在农民约翰的田地不同的地方。我们用一个N×M(1≤N≤100,1≤M≤100) 的网格图表示。每个网格中有水(W) 或是旱地(.)。一个网格与其周围的八个网格相连,而一组相连的网格视为一个水坑。约翰想弄清楚他的田地已经形成了多少水坑。给出约翰田地的示意图,确定当中有多少水坑。

输入第 11 行:两个空格隔开的整数:N 和 M。

第 22 行到第 N+1 行:每行 M 个字符,每个字符是 W 或 .,它们表示网格图中的一排。字符之间没有空格。

输出一行,表示水坑的数量。

输入输出样例

输入 #1复制

10 12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.

输出 #1复制

3

说明/提示

OUTPUT DETAILS: There are three ponds: one in the upper left, one in the lower left, and one along the right side.

解题

这题与前一个题有共同之处,不过我们要统计的是水坑的个数

还是一样的搜索思路,每次搜到一个没走过的#就计数+1,然后遍历这个水坑(都记录为走过--用book数组就能实现) 以此类推。。。

不过这个题有八个方向,坐标数组和循环次数要修改一下

package dfs;

import java.util.Scanner;
//一遍ac 
public class Lake_Counting_S {
static final int N =105;
static char [][]map= new char[N][N];
static boolean [][]book= new boolean[N][N];
static int n;
static int m ;
static int dx[]= {1,1,1,0,0,-1,-1,-1};
static int dy[]= {-1,0,1,1,-1,1,0,-1};
static int cnt=0;//记录
 public static void main(String[] args) {
	Scanner sc=new Scanner(System.in);
	n=sc.nextInt();
	m=sc.nextInt();
	sc.nextLine();
	for(int i=0;i<n;i++)
	{
		String s=sc.nextLine();
		map[i]=s.toCharArray();
	}
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			if(map[i][j]=='W'&&!book[i][j]) 
				{cnt++;
				dfs(i,j);}
		}
	}
	System.out.println(cnt);
	
}
 static void dfs(int x,int y)
 {
	 for(int i=0;i<8;i++)
	 {
		 int a=x+dx[i];
		 int b=y+dy[i];
		 if(a<0||a>=n||b<0||b>=m) continue;
		 if(map[a][b]!='W') continue;
		 if(book[a][b]) continue;
		 book[a][b]=true;
		 dfs(a,b);
	 }
 }
}

是不是有点感觉了?看会了不代表真会了  记得自己手敲a一遍哦~

不过瘾 ?再来一个棋盘问题

1114. 棋盘问题 - AcWing题库

在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。

要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放 k 个棋子的所有可行的摆放方案数目 C。

输入格式

输入含有多组测试数据。

每组数据的第一行是两个正整数 n,k,用一个空格隔开,表示了将在一个 n∗n 的矩阵内描述棋盘,以及摆放棋子的数目。当为-1 -1时表示输入结束。

随后的 n 行描述了棋盘的形状:每行有 n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。

输出格式

对于每一组数据,给出一行输出,输出摆放的方案数目 C (数据保证 C<231)。

数据范围

n≤8,k≤n

输入样例:
2 1
#.
.#
4 4
...#
..#.
.#..
#...
-1 -1
输出样例:
2
1

这个题是八皇后的一个变式,想了解母题的可以看我之前的博文,有一篇专门讲八皇后的~

这题在棋盘上设置了障碍,并且可以放旗子的个数可变

思路还是跟解决八皇后的一样,以行为递归深度,看看每一列能不能放棋子

遇到障碍就跳过

因为棋子数目不等于n了,我们可以引入一个cnt变量,记录已经放了多少棋子了

最后特别注意一下 k<n 的情况,比如样例中2  1

可能我们已经放了k个棋子,但是棋盘还没有遍历完,后面的情况会漏掉

所以我们强行遍历下一行,看看能不能再放(代码最后一行)

package dfs;

import java.util.Scanner;
//6分钟拿下
//n皇后变式
public class _1114_棋盘问题 {
 static char map[][]=new char [10][10];
 static boolean col[]=new boolean [10];//记录该列有没有放棋子
  static int n;
  static int k;
  static int ans=0;
 public static void main(String[] args) {
	Scanner sc=new Scanner(System.in);
	while(sc.hasNext())
	{
	n=sc.nextInt();
	k=sc.nextInt();
	if(k==-1&&n==-1) break;
	sc.nextLine();
   for(int i=0;i<n;i++)
   {
	   String s=sc.nextLine();
	   map[i]=s.toCharArray();
   }
   ans=0;//每次都要重置答案
   dfs(0,0);
   System.out.println(ans);
 } }
 static void dfs(int row,int cnt)//row 当前所在行 cnt 当前放了棋子
 {
	 if(row>n) return;
	 if(cnt==k) {ans++;return;}
		 for(int i=0;i<n;i++)
		 {
			 if(col[i]) continue;//这一列放过了
			 if(map[row][i]=='.') continue;//排除空白区域
			 col[i]=true;
			 dfs(row+1,cnt+1);
			 col[i]=false;//回溯
		 }
		 dfs(row+1,cnt);//强行看下一行还能不能放棋子 应对棋子个数少的情况
 }
}

[NOIP2001 提高组] 数的划分 - 洛谷

来个组合问题

将整数 n 分成 k 份,且每份不能为空,任意两个方案不相同(不考虑顺序)。

例如:n=7,k=3,下面三种分法被认为是相同的。

1,1,5
1,5,1
5,1,1.

问有多少种不同的分法。

输入格式

n,k (6<n≤200,2≤k≤6)

输出格式

11 个整数,即不同的分法。

输入输出样例

输入 #1复制

7 3

输出 #1复制

4

说明/提示

四种分法为:
1,1,5
1,2,4
1,3,3
2,2,3

【题目来源】

NOIP 2001 提高组第二题

题解

依次枚举k个数,让他们的和加起来等于n

注意每个数字是可以重复使用的,比如1 1 5

每找到一组答案就 记录 回溯、、、

所以不需要book数组记录有没有用过了hhh

那么这题的难度在于剪枝

1.剪枝1比如为了防止每一轮都从1开始枚举(n很大的时候一定不满足)

我们可以增加一个start变量,下一个数从当前的i开始枚举,保证枚举的数递增(题目的分法也是递增的)

2.剪枝2假设n=8 前面sum已经等于三  index位置放了4 那下一个位置最小还是放4,但这个时候已经超过n了 这种情况就应该剪掉

目前还能放k-index+1个,假设每次都放最小的 就是当前的 i 

那么符合能保证加到n的条件就是 sum+i*(k-index+1)<=n

package dfs;

import java.util.Scanner;


//怎么去重?
public class 数的划分 {
	static int n;
	static int k;
	static int a[]=new int [205];
	static int ans=0;
public static void main(String[] args) {
	Scanner sc=new Scanner(System.in);
	n=sc.nextInt();
	k=sc.nextInt();
	dfs(1,1,0);
	System.out.println(ans);
	
}
static void dfs(int index,int start,int sum)
{

	if(sum>n) return;
	if(index>k) 
	{
	if(sum==n) {
		ans++;
	
	}
	return;
	}
	
	for(int i=start;sum+(k-index+1)*i<=n;i++)
	{
		a[index]=i;
		dfs(index+1,i,sum+i);
		a[index]=0;
	}
		
		
	}
}

[NOIP2004 普及组] 火星人 - 洛谷

来个有意思的   这个题炼习一下排列

人类终于登上了火星的土地并且见到了神秘的火星人。人类和火星人都无法理解对方的语言,但是我们的科学家发明了一种用数字交流的方法。这种交流方法是这样的,首先,火星人把一个非常大的数字告诉人类科学家,科学家破解这个数字的含义后,再把一个很小的数字加到这个大数上面,把结果告诉火星人,作为人类的回答。

火星人用一种非常简单的方式来表示数字――掰手指。火星人只有一只手,但这只手上有成千上万的手指,这些手指排成一列,分别编号为 1,2,3,⋯。火星人的任意两根手指都能随意交换位置,他们就是通过这方法计数的。

一个火星人用一个人类的手演示了如何用手指计数。如果把五根手指――拇指、食指、中指、无名指和小指分别编号为 1,2,3,4和 5,当它们按正常顺序排列时,形成了 5 位数 12345,当你交换无名指和小指的位置时,会形成 5 位数 12354,当你把五个手指的顺序完全颠倒时,会形成 54321,在所有能够形成的 120 个 5 位数中,12345 最小,它表示 1;12354 第二小,它表示 2;54321 最大,它表示 120。下表展示了只有 3 根手指时能够形成的 6 个 3 位数和它们代表的数字:

三进制数代表的数字
1231
1322
2133
2314
3125
3216

现在你有幸成为了第一个和火星人交流的地球人。一个火星人会让你看他的手指,科学家会告诉你要加上去的很小的数。你的任务是,把火星人用手指表示的数与科学家告诉你的数相加,并根据相加的结果改变火星人手指的排列顺序。输入数据保证这个结果不会超出火星人手指能表示的范围。

输入格式

共三行。
第一行一个正整数 N,表示火星人手指的数目(1≤N≤10000)。
第二行是一个正整数 M,表示要加上去的小整数(1≤M≤100)。
下一行是 1 到 N 这 N 个整数的一个排列,用空格隔开,表示火星人手指的排列顺序。

输出格式

�N 个整数,表示改变后的火星人手指的排列顺序。每两个相邻的数中间用一个空格分开,不能有多余的空格。

输入输出样例

输入 #1复制

5
3
1 2 3 4 5

输出 #1复制

1 2 4 5 3

说明/提示

对于 30%30% 的数据,N≤15。

对于 60%60% 的数据,N≤50。

对于 100%100% 的数据,N≤10000。

noip2004 普及组第 4 题

对于排列问题,我们要用一个book数组记录这个数有没有被使用过,(不可重复)

我们只需要找到从火星人的排列,开始枚举“那个很小的数”次即可

为了防止超时,我们应该尽快找到火星人的排列,那么我们不妨按照火星人的第一个数字来找。

这个题卡java 可能有个样例超时,用 c++没问题

package dfs;

import java.util.Scanner;

public class 火星人 {
	static int a[]=new int [10010];//存放答案
	static int n;
	static int m;
	static int cnt=0;
	static int mar[]=new int [10010];//存放火星人给的数
	static boolean book[]=new boolean [10010];//判断数字使用情况
	static boolean return0=false;//找到答案,结束搜索
 public static void main(String[] args) {
	Scanner sc=new Scanner(System.in);
	n=sc.nextInt();
	m=sc.nextInt();
	for(int i=1;i<=n;i++)
	{
		mar[i]=sc.nextInt();
		
	}
	dfs(1);
}
 static void dfs(int step)
 {
	if(return0) return;
	 
	 if(step>n) 
		 { cnt++;
		
		 if(cnt==m+1) {
			 return0=true;
		 for(int i=1;i<=n;i++)
		 System.out.print(a[i]+" ");
		// System.out.println();
		 }
		 return;
		 }
	 
	 
		 for(int i=1;i<=n;i++)
		 {
			 if(cnt==0) i=mar[step];//剪枝 从火星人提供的第一个数开始搜
			 if(book[i]!=false) continue;
			 book[i]=true;
			 a[step]=i;//注意这些索引别写错了
			 dfs(step+1);
			 a[step]=0;
			 book[i]=false;
		 }
 }
}

最爆肝的一集,回馈大家支持了(喜)

好好练,不会就多看多写,你一定能学会dfs!

新人博主,点关注,不迷路~

  • 14
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值