【算法】DFS模板

DFS的核心思想是循环嵌套,将一个大问题拆分成一个个相同小问题,然后将小问题(递归的每一轮)进行判断

模板

void dfs(int step)    //步长
{
    if(/*跳出循环的条件*/){
        return;    //return十分关键,否则循环将会无法跳出
    }
    /*函数主体
    对功能进行实现*/
    for(/*对现有条件进行罗列*/){
        if(/*判断是否合理*/){
            //将条件修改
            dfs(/*新的step*/)
            /*!重中之重,当跳出那层循环后将数据全部归位*/
        }    
    }
}

矩阵模板

int f[4][2]={{0,1},{0,-1},{1,0},{-1,0}};    //用于判断下一步怎么走向几个方向走就是几个数据
void dfs(int x,int y){        //进入点的坐标
    if(/*跳出循环的条件*/){
        /*作出相应操作*/
        return;        //不要忘了return
    }
    for(int i=0;i</*f的长度*/;i++){
        int x0=x+f[i][0];    
        /*此处是更新点的坐标,注意是直接让原来的点加上这个数据,不是直接等于*/
        int y0=y+f[i][1];
        if(/*用新坐标x0,y0判断是否符合条件*/){
            dfs(x0,y0);    //用新的坐标进行递归
        }
    }
}

注意事项

1.判断条件成功后一定要加return来跳出循环

2.当递归调用结束后一定记得将数据修改回来

可以应用的场景

1.数据的选择,比如从几个数据中选出其中一部分进行判断(CF6A Triangle、P1036 [NOIP2002 普及组] 选数、P1460 [USACO2.1]健康的荷斯坦奶牛 Healthy Holsteins)

2.给出一个矩阵,让你判断一些数据成立的条件(CF629A Far Relative’s Birthday Cake、CF445A DZY Loves Chessboard、P1596 [USACO10OCT]Lake Counting S)

3.两个元素之间插入指定要求的数据(P7200 [COCI2019-2020#1] Lutrija)

4.对数据进行排列(P1706 全排列问题)

常见的扩展应用

(一)特殊方方向判断的DFS(只向正方向判断,不向负方向判断)

例题:CF629A Far Relative’s Birthday Cake(洛谷)

#include<bits/stdc++.h>
using namespace std;
char a[105][105];
int n;
int sum=0;
int i,j;
int temp[105][105]={{0,0}};
bool check(int x,int y){        //递归并不会只是同一行或者同一列的数据,需要加条件进行判断
	if(x==i&&y<n)	//是不是同一行 
		return true;
	if(y==j&&x<n)	//是不是同一列 
		return true;
	return false;
}
void dfs(int x,int y){
	if(check(x,y)){
		if(a[x][y]=='C'&&temp[x][y]==0){
			sum++;		
		}
		dfs(x,y+1);    //只需要判断这个点正方向的点的情况,负方向情况不用记录
		dfs(x+1,y);
	}
	
}
int main(){
	cin>>n;
	for(i=0;i<n;i++){
		for(j=0;j<n;j++){
			char ch;
			ch=getchar();
			if(ch=='\n')
				ch=getchar();    
               /*这么输入的本意是去除掉换行符,
                但是有时候会出大问题,
                导致输入格式出现错误,
                可以直接用cin输入,cin会自动过滤掉换行符*/
			a[i][j]=ch;
		}
	}
	for(i=0;i<n;i++){
		for(j=0;j<n;j++){
			if(a[i][j]=='C'){
				temp[i][j]=1;
				dfs(i,j);
				temp[i][j]=0;
			}
		}
	}
	cout<<sum;
}

本题注意事项:

1.输入时的代码其实不应该这么写,可能会导致输入错误?

2.本题只需要判断进入函数数据正方向的数据,负方向的不用判断

3.每次进入只需判断同一行和同一列的数据,但是递归不只是代表同一行和同一列的数据,所以应该加上判断

(二)从符合条件的点向四个方向扩展的DFS

例题:CF629A Far Relative’s Birthday Cake

#include<bits/stdc++.h>
using namespace std;
char a[105][105];
char b[105][105];
int toy[5]={1,0,-1,0};
int tox[5]={0,1,0,-1};
int n,m;
void dfs(int i,int j,int b){
	if(i<0||j<0||i>n||j>m){
		return;
	} 
	if(b==1){        //判断本棋盘格应该是B还是W
		a[i][j]='B';
	}
	else{
		a[i][j]='W';
	}
	for(int k=0;k<4;k++){        //判断结束后向这个判断完的棋盘格的四个方向进行扩展搜索
		int x0=i+tox[k];        
        /*因为已经知道这个棋盘格是B还是W,所以和他相邻的棋盘格一定是和他字母不一样的*/
		int y0=j+toy[k];
		if(a[x0][y0]=='.'){
			if(b==0)
				dfs(x0,y0,1);
			else
				dfs(x0,y0,0);
		}
	}
}
int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			char ch;
			ch=getchar();
			if(ch=='\n')
				ch=getchar();
			a[i][j]=ch;
		}
	}
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			if(a[i][j]=='.'){
				dfs(i,j,1);        //当符合空棋盘的条件时进入深搜
			}
		}
	}
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			cout<<a[i][j];
		} 
		cout<<endl;
	}
}

注意事项

1.本题核心思想:本题是在找到空白位置的基础上进入递归循环,当确定这个空白位置是黑还是白(W还是B),然后在对这个点的四个方向进行判断,如果这个点的四个方向还有空白棋盘的话,说明肯定和本棋盘格的颜色不同,所以递归时换个颜色。

(三)DFS的去重操作

例题:P1036 [NOIP2002 普及组] 选数(洛谷)

#include<bits/stdc++.h>
using namespace std;
int n,k;
int a[21]={0};
bool f[21];
int num=0;
long long sum=0;
void dfs(int start,int step){        //引入一个新的start变量来进行去重
	if(step==k){
		bool flag=true;
		if(sum==0||sum==1){
			return;
		}
		if(sum==2){
			num++;
			return;
		}
		for(long long i=2;i<=sqrt(sum);i++){
			if(sum%i==0)
				return;
		}
		num++;
		return;
	}
	for(int i=start;i<n;i++){        //start用于for循环的判断
		sum+=a[i];
		step++;
		dfs(i+1,step);
		step--;
		sum-=a[i];
		f[i]=false;
	}
}
int main(){
	cin>>n>>k;
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	dfs(0,0);
	cout<<num;
}

注意事项:

1.本题的去重思想是在递归中引入一个新的变量start用于函数内循环的判断

此处start的就是只能从没有比上一个数据大的地方进项筛选,类似于

for(int i=0;i<n;i++){
    for(int j=i;j<n;j++){
        for(int k=j;k<n;k++)
    }
}

(四)对于不合适数据的跳过

例题:P1460 [USACO2.1]健康的荷斯坦奶牛 Healthy Holsteins

#include<bits/stdc++.h>
using namespace std;
int a[1005];
int b[1005][1005];
int c[1005];		//代表遍历每一步的结果
int ans[1005];		//代表最终结果,最小的那个
int m,n;
int mmin = 100000000;
bool Judge(int x){			//一共选中x种饲料
	for(int i=1;i<=m;i++){
		int sum=0;
		for(int j=1;j<=x;j++){
			sum+=b[c[j]][i];
		}
		if(a[i]>sum)	return false;
	}
	return true;
}
void dfs(int t,int s){		//t表示第t类饲料 ,s表示所选饲料的总数
	if(t>n){	//说明饲料总种类数到达边界
		if(Judge(s)){		//判断是不是所有营养都能满足
			if(s<mmin) {	//判断s种饲料是不是最小的饲料
				mmin=s;
				for(int i=1;i<=mmin;i++){
					ans[i]=c[i];
				}
			}
		}
		return;
	}
	/*未达到边界*/
	 c[s+1]=t;
	 dfs(t+1,s+1);
	 c[s+1]=0;	//回溯
	 dfs(t+1,s);	//不选第t种饲料的操作
}
int main(){
	cin>>m;
	for(int i=1;i<=m;i++){
		cin>>a[i];
	}
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>b[i][j];
		}
	}
	dfs(1,0);
	cout<<mmin<<" ";
	for(int i=1;i<=mmin;i++){
		cout<<ans[i]<<" ";
	}
}

注意事项:

1.核心代码就是这几行

c[s+1]=t;
dfs(t+1,s+1);
c[s+1]=0;	//回溯
dfs(t+1,s);	//不选第t种饲料的操作

DFS核心就是回溯

漫水算法

例题:CF6B President's Office

#include<bits/stdc++.h>
using namespace std;
int m,n;
int f[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
char a[105][105];
char ch;
set<char> s;
void dfs(int x,int y){ 
    /*核心就是通过DFS来对相连图形进行填充,然后输出需要填充几块*/
	for(int i=0;i<4;i++){
		int x0=x+f[i][0];
		int y0=y+f[i][1];
		if(x0<0||y0<0||x0>=m||y0>=n){
			continue;
		}
		else if(a[x0][y0]!='.'){
			if(a[x0][y0]!=ch){
				s.insert(a[x0][y0]);
				a[x0][y0]='.';
			}
			else{
				a[x0][y0]='.';
				dfs(x0,y0); 
			} 
		}
	}
	return;
}
int main(){
	cin>>m>>n>>ch;
	for(int i=0;i<m;i++){
		for(int j=0;j<n;j++){
			cin>>a[i][j];
		}
	}
	for(int i=0;i<m;i++){
		for(int j=0;j<n;j++){
			if(a[i][j]==ch){
				dfs(i,j);
				break;
			}
		}
	}
	cout<<s.size();
}

注意事项:

漫水的核心就是由一个点扩展到所有这一片所有的点进行覆盖

例题:P1506 拯救oibh总部

#include<bits/stdc++.h>
using namespace std;
int m,n;
char a[505][505];
int cx,cy;
int dx,dy;
int num;
int f[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
void dfs(int x,int y){
	a[x][y]='1';
	for(int i=0;i<4;i++){
		int x0=x+f[i][0];
		int y0=y+f[i][1];
		if(x0>0&&x0<=m&&y0>0&&y0<=n&&a[x0][y0]=='0')	dfs(x0,y0);
	    /*只要接触到边缘就说明没有被围住*/
    }
	return;
}
int main(){
	cin>>m>>n;
	int sum=0;
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++){
			cin>>a[i][j];
		}
	}
	for(int i=1;i<=m;i++){        
		if(a[i][1]=='0')
			dfs(i,1);
		if(a[i][n]=='0')
			dfs(i,n);
	}
	for(int j=2;j<n;j++){
		if(a[1][j]=='0')
			dfs(1,j);
		if(a[m][j]=='0')
			dfs(m,j);
	}
	for(int k=1;k<=m;k++){
		for(int l=1;l<=n;l++){
			if(a[k][l]=='0'){
				sum++;
			}
		}
	}
	cout<<sum;
}

注意事项:

本体的核心是判断被围起来的有几个点,漫水算法需要稍作改变,因为只要接触到边缘就说明没有被围住,

所以从四条边的周围向内漫水,所有能接触到的点说明都没有被围起来,最后在统计没有被着色的点就是题目中的答案

例题:P1331 海战

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int c,r; 
char map[1005][1005];
int po[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
int fx[4]={0,-1,1,0};
int fy[4]={-1,0,0,1};
void dfs(int x,int y){
	map[x][y]='*';
	for(int i=0;i<4;i++){
		if(x+fx[i]>0&&x+fx[i]<=r&&y+fy[i]>0&&y+fy[i]<=c&&
		map[x+fx[i]][y+fy[i]]=='#')dfs(x+fx[i],y+fy[i]);
	}
}
bool Judge(int i,int j){
	int c=0;
	if(map[i][j]=='#')c++;
	if(map[i+1][j]=='#')c++;
	if(map[i][j+1]=='#')c++;
	if(map[i+1][j+1]=='#')c++;
	if(c==3)return 0;
	return 1;
}
int main(){
	int num=0;
	cin>>r>>c;
	for(int i=1;i<=r;i++){
		for(int j=1;j<=c;j++){
			cin>>map[i][j];
		}
	}
	for(int i=1;i<=r;i++){
		for(int j=1;j<=c;j++){
			if(i<r&&j<c&&Judge(i,j)==0){
				printf("Bad placement.");
				return 0;
			}
		}
	} 
	for(int i=1;i<=r;i++){
		for(int j=1;j<=c;j++){
			if(map[i][j]=='#'){
				num++;
				dfs(i,j);
			}
		}
	}
	printf("There are %d ships.",num); 
	
}
/*#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int r,c;
char map[1010][1010];
int fx[4]={0,-1,1,0};
int fy[4]={-1,0,0,1};
int dfs(int x,int y){
	map[x][y]='*';
	for(int i=0;i<4;i++){
		if(x+fx[i]>0&&x+fx[i]<=r&&y+fy[i]>0&&y+fy[i]<=c&&
		map[x+fx[i]][y+fy[i]]=='#')dfs(x+fx[i],y+fy[i]);
	}
}//把与#连通的所有点改成*因为它们是同一艘船 
bool d(int i,int j){
	int c=0;
	if(map[i][j]=='#')c++;
	if(map[i+1][j]=='#')c++;
	if(map[i][j+1]=='#')c++;
	if(map[i+1][j+1]=='#')c++;
	if(c==3)return 0;
	return 1;
}//判断是否合法 
int main(){
	scanf("%d%d",&r,&c);
	register int i,j;
	for(i=1;i<=r;i++){
		for(j=1;j<=c;j++){
		cin>>map[i][j];
		}
	}
	int s=0;
	for(i=1;i<=r;i++){
		for(j=1;j<=c;j++){
			if(i<r&&j<c&&d(i,j)==0){
				printf("Bad placement.");
				return 0;//不合法后面就没必要继续了 
			}
		}
	}
	for(i=1;i<=r;i++){
		for(j=1;j<=c;j++){
			if(map[i][j]=='#'){
			s++;
			dfs(i,j);	
			}//因为前面已经确保了是合法的,现在只需统计船的数量 
		}
	}
	printf("There are %d ships.",s);
	return 0;
}*/

注意事项:

本题在漫水算法的基础上加入了判断是否是船只,只需要将整个表遍历,判断它本身,它右边,它下面,它右下四个点,只要不是三个就说明合法,其他的都一样

  • 3
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值