【深度优先搜索/DFS】[11.9](7/20)

P1506 拯救oibh总部

读字符千万注意会不会读到换行符!!
染色问题直接在地图外面多加一圈可染色的,再从这个圈的顶点开始染,以确保dfs能染到边边角角

#include<stdio.h>
#include<string.h>
char ch;
int n,m,map[505][505];
int dx[4]={0,0,-1,1};
int dy[4]={1,-1,0,0};
void dfs(int x,int y)
{
	if(x<0||x>n+1||y<0||y>m+1||map[x][y]) return;
	map[x][y]=2;
	for(int i=0;i<=3;i++)
	{
		dfs(x+dx[i],y+dy[i]);
	}
}
int main()
{
	int ans=0;
	memset(map,0,sizeof(map));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf(" %c",&ch);
//赋值完后打印地图有误,调试后才发现在确定长宽之后多读了一个换行符
//!!!!
			ch=='0'?map[i][j]=0:map[i][j]=1;
		}
	}
	dfs(0,0);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(!map[i][j]) ans++;
		}
	}
	printf("%d",ans);
	return 0;
}

P1605 迷宫

超基础dfs,一交全部MLE,本来开了个10000的结构体,删了,还是MLE,最后把数组(才开到10000啊)改成10终于过了(

#include<stdio.h>
#include<string.h>
int ans=0;
int n,m,t;
int begin_x,begin_y,final_x,final_y;

int map[10][10];
int dx[4]={-1,1,0,0};
int dy[4]={0,0,1,-1};

void dfs(int x,int y)
{
	if(x==final_x&&y==final_y) {ans++;return;}
	else{
		map[x][y]=2;
		for(int j=0;j<=3;j++){
			if(x+dx[j]<1||y+dy[j]<1||x+dx[j]>n||y+dy[j]>m) continue;
			if(map[x+dx[j]][y+dy[j]]==0){
					map[x+dx[j]][y+dy[j]]=2;
					dfs(x+dx[j],y+dy[j]);
					map[x+dx[j]][y+dy[j]]=0;
				}
			}
		}
}

int main()
{
	int x,y;
	memset(map,0,sizeof(map));
	scanf("%d%d%d",&n,&m,&t);
	scanf("%d%d%d%d",&begin_x,&begin_y,&final_x,&final_y);
	for(int i=1;i<=t;i++) 
	{
	scanf("%d%d",&x,&y);
	map[x][y]=1;
	}
	dfs(begin_x,begin_y);
	printf("%d",ans);
	return 0;
}

P1101 单词方阵

用一个标记用二维数组vis,将成单词的格子都标记为1,用于输出
先找y,再向y的八个方向找i,找到i就记录方向,进入深搜
用ans计数,同时用来更新下一个要匹配的字母(注意要写a+1,不能写a++!!)
因为不能找到一个就标记一个(因为可能某条路其实不能成词),所以先用一个结构体记下匹配的(x,y),确定成词之后再用它作标记

#include<stdio.h>
#include<string.h>
struct node
{
    int x,y;
}c[110];

char map[101][101];
char word[]="yizhong";
int vis[101][101];
int dirx,diry;// 可行的方向变量 

void dfs(int x,int y,node c[],int ans)
{
	if(ans==7){
		for(int i=0;i<7;i++)
		vis[c[i].x][c[i].y]=1;
	}
	else{
		if(ans==6||map[x][y]==word[ans]){
			c[ans].x=x;c[ans].y=y;
			dfs(x+dirx,y+diry,c,ans+1);//千万不能写a++!!
		}
	}
}

int main()
{
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++) scanf("%s",map[i]);
	memset(vis,0,sizeof(vis));
	for(int x=0;x<n;x++){
		for(int y=0;y<n;y++){
			if(map[x][y]=='y'){//找到'y'后向y的八个方向找i
				for(int dx=-1;dx<=1;dx++){
				for(int dy=-1;dy<=1;dy++){
					if(map[x+dx][y+dy]=='i'){
						//记录方向
						dirx=dx;
						diry=dy;
						dfs(x,y,c,0); //从‘y’开始深搜
					}
				}
				}  
			}
		}
	}
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			if(vis[i][j]) printf("%c",map[i][j]);
			else printf("*");
		}
		printf("\n");
	}
	return 0;
}

P1036 选数

本题要求的操作分两部分:
一是素数判定(很常用的样子)
二是深度优先搜索

卡了将近半个月之后菜鸡终于看懂了题解(
画图辅助+调试看计算过程
递归在此时展现了它反复横跳(竖跳XD)的魅力(
sum的用处是在调试了两遍之后才后知后觉地反应过来。主要是因为纠结代码运行纠结得太久,以至于忘记了题目本来是要让我求什么数的
之后做点其他深搜题加强理解

//解法一:利用一个for循环来确保遍历了所有k个加数的组合
#include<stdio.h>
#include<math.h>
int x[20];
int n,k;

bool isprime(int n){
    int s=sqrt(double(n));
    for(int i=2;i<=s;i++){
        if(n%i==0)return false;
    }
    return true;
}

int solve(int leftnum,int summing,int start,int end)
//还要加上的数的个数,已经得到的和,在哪一个范围内选择下一个数 
{
	if(leftnum==0) return isprime(summing);
	//要求的个数已经加完,一次深搜结束,返回的结果直接送去判定 
	//切勿把这一条放在最后,否则sum直接清零。 
	int sum=0;
	for(int i=start;i<=end;i++){//  开始/继续深搜 
		sum+=solve(leftnum-1,summing+x[i],i+1,end);
		//在加数的过程中范围不断收窄 
		//sum用于统计为素数的和的个数
		//由于素数判定函数用的是bool类型,当其为真时此处会返回1 
	}
	return sum; 
}

int main()
{
	scanf("%d%d",&n,&k);
	for(int j=0;j<n;j++){
		scanf("%d",&x[j]);
	}
	printf("%d",solve(k,0,0,n-1));//注意此处设定的右边界与数组下标的关系 
	return 0;
}
//解法二:对于待选数x[i],选择加上,或者不加上
#include<stdio.h>
#include<math.h>
int x[20];
int n,k;
int sum=0;

int isprime(int n){//判断是否质数
    int s=sqrt(double(n));
    for(int i=2;i<=s;i++){
        if(n%i==0)return 0;
    }
    return 1;
}

//我需要实现遍历每一条支路,而不是有一个解就直接结束 
void solve(int leftnum,int summing,int i)
{
	if(leftnum==0||i==n){
		if(isprime(summing)&&leftnum==0) sum++; 
		//leftnum==0的条件千万别漏,因为情况可能是已经加到数组最后一个数而加数数量还不够 
		return;//就是这个!!直接返回上一层!! 
	}
	solve(leftnum-1,summing+x[i],i+1);
	solve(leftnum,summing,i+1);
	return;//返回上一层 
}

int main()
{
	scanf("%d%d",&n,&k);
	for(int j=0;j<n;j++){
		scanf("%d",&x[j]);
	}
	solve(k,0,0);
	printf("%d",sum);
	return 0;
}

解法二走了很多弯路。
首先这个方法和白书上例题很像,但例题只要求判断“能否”,故用了bool类型的函数,只要找到一个解就层层上报好消息直接结束,没找到解才返回上一层继续往下找。
但本题要求遍历所有组合,难住我的是我应该要写一个什么类型的函数,每一次dfs的终点应该向上一层返回什么玩意才能做到回溯的效果。
然后是void,一次dfs完毕直接return就完事了。
emmmm…吸取教训,继续努力


题目:给定任意正整数n(没有范围其实是不行的,这里姑且认为50以内吧),输出1-n的所有可能的排列并输出种数
(其实是阿萧的C语言作业(?????

#include<stdio.h>
int a[500];
int ans=0;
int n;
void swap(int i,int j){
	int t=a[i];
	a[i]=a[j];
	a[j]=t;
}
void dfs(int start,int end){
	if(start==n){
		for(int i=1;i<=n;i++)
			printf("%d",a[i]);
		printf("\n");
		ans++;
	}
	for(int i=start;i<=end;i++){
		swap(start,i);
		dfs(start+1,end);
		swap(start,i);
	}
}

int main()
{
	scanf("%d",&n);
	//创建一个包含1至n所有数字的数组 
	for(int i=1;i<=n;i++){
		a[i]=i;
	}
	dfs(1,n);
	printf("%d",ans);
	return 0;
}

DFS树

要点:
1.for循环保证了第i个数能与其他位置的数依次交换,且保证第i层仅做第i个数与其他数的交换。
2.数的交换发生在每一次DFS开始之前,为保证能回到原来状态做另外的交换,每次DFS结束后都要换回去。
3.不交换本身也是全排列的一种。

还有一个比较容易想到的解法:
对于当前待填的位置,尝试所有未尝试过的数

#include<stdio.h>
void dfs(int n,int*a,int pos){ 
//n用于判断序列是否已经填完,数组a为已填数列,pos为当前待填位置
	if(pos==n){
		for(int i=0;i<n;i++) printf("%d",a[i]);
		printf("\n");
	} 
	else
		for(int i=1;i<=n;i++){ //尝试填入各种数字 
			int ok=1;
			for(int j=0;j<pos;j++)
			//j<pos一度错填为j<n导致出错
			//当条件为j<n时,回溯之后会一直有ok=0导致只出现123
				if(a[j]==i) ok=0; //判断当前挑选的数字是否已填过
			if(ok) {
				a[pos]=i; 
				dfs(n,a,pos+1);	
			}
		}
}

int main()
{
	int n;
	int a[1000]={0};
	scanf("%d",&n);
	dfs(n,a,0);
	return 0;
} 

UVA524 素数环

一个长度为n的数组,规定第一个为1,剩下n-1个格子就用2-n的正整数逐个试填,填完后特判检查,输出。
注意格式:最后一个数字后面不能有空格,每组输出之间要有空行

#include<stdio.h>
#include<math.h>
int a[17];
int n;

bool isprime(int n){
    int s=sqrt(double(n));
    for(int i=2;i<=s;i++){
        if(n%i==0)return false;
    }
    return true;
}
void dfs(int i){
	if(i==n+1&&isprime(a[i-1]+1)){
		printf("%d",a[1]);
		for(int x=2;x<=n;x++) printf(" %d",a[x]);
		printf("\n");
	}
	else{
		for(int j=2;j<=n;j++){
			int ok=1;
			for(int k=1;k<=n;k++){
				if(j==a[k])	{
					ok=0;
					break;
				}
			}
			if(ok){
				if(isprime(j+a[i-1])) {
				a[i]=j;
				dfs(i+1);
				a[i]=0;
		//!!!注意此处要清除格子,才能回溯试填该格子的下一个数字!
		//否则输出只有一种排列
				}
			}
		}
	}
}

int main()
{
	int i=0;
	int flag=0;
	while(scanf("%d",&n)!=EOF){
	if(flag) printf("\n");
	flag=1;
	printf("Case %d:\n",++i);
	a[1]=1;
	dfs(2);
	}
	return 0;	
}

虽然紫书上的写法要简洁得多,但毕竟是第一个独立流畅写完的dfs,还是要表扬一下自己

发布了28 篇原创文章 · 获赞 0 · 访问量 685

猜你喜欢

转载自blog.csdn.net/weixin_45561591/article/details/101780641