【BFS广度优先搜索专题】&【蓝桥杯备考训练】:母亲的牛奶、走迷宫、八数码1、八数码2、全球变暖【已更新完成】

文章探讨了如何使用编程解决实际问题,如农夫约翰倒牛奶的算法、二维迷宫的最短路径和八数码游戏的最优操作序列,以及全球变暖背景下岛屿淹没的计算,涉及广度优先搜索和深度优先搜索策略。
摘要由CSDN通过智能技术生成

目录

1、母亲的牛奶(usaco training 1.5)

2、走迷宫(模板)

3、八数码1(模板)

4、八数码2(《算法竞赛进阶指南》& HDU1043)

5、全球变暖(第九届蓝桥杯省赛C++ & JAVA  A组/B组)


1、母亲的牛奶(usaco training 1.5)

农夫约翰有三个容量分别为 A,B,C 升的挤奶桶。

最开始桶 A 和桶 B 都是空的,而桶 C 里装满了牛奶。

有时,约翰会将牛奶从一个桶倒到另一个桶中,直到被倒入牛奶的桶满了或者倒出牛奶的桶空了为止。

这一过程中间不能有任何停顿,并且不会有任何牛奶的浪费。

请你编写一个程序判断,当 A 桶是空的时候,C 桶中可能包含多少升牛奶,找出所有的可能情况。

输入格式

共一行,包含三个整数 A,B,C

输出格式

共一行,包含若干个整数,表示 C 桶中牛奶存量的所有可能情况,请将这些数字按升序排列。

数据范围

1≤A,B,C≤20

输入样例1:
8 9 10
输出样例1:
1 2 8 9 10
输入样例2:
2 5 10
输出样例2:
5 6 7 8 9 10
思路:

枚举每一种情况,存到vis[][][]数组中,最后进行遍历,把a=0的情况输出出来

代码:
#include<bits/stdc++.h>

using namespace std;

const int N=25;

int A,B,C;//容量 

int cc=C,ca=0,cb=0;//c桶7装满了奶,而a桶和b桶是空的


bool vis[N][N][N];

int hh,tt;
struct node 
{
	int a,b,c;
}q[N*N*N];

void insert(int a,int b,int c)
{
	if(!vis[a][b][c])
	{
		q[++tt]={a,b,c};
		vis[a][b][c]=true;	
	}
}

void bfs()
{
	q[0]={0,0,C};
	vis[0][0][C]=true;
	while(hh<=tt)
	{
		auto t=q[hh++];
		int a=t.a;
		int b=t.b;
		int c=t.c;

        
        //a to b 
		insert(a - min(a ,B - b),min(a + b, B ), c);
		//a to c
		insert(a - min(a, C - c), b, min(C , c + a));
		//b to a;
		insert(min(A,a+b),b-min(b,A-a),c);
		//b to c
		insert(a,b-min(b,C-c),min(c+b,C));
		//c to a;
		insert(min(A,a+c),b,c-min(A-a,c));
		//c to b
		insert(a,min(b+c,B),c-min(B-b,c));
		
		
	}
	
}

int main()
{
	cin>>A>>B>>C;
	//cout<<A<<B<<C;
	bfs();
	//cout<<vis[0][9][1];
	for(int i=0;i<=C;i++)
		for(int j=0;j<=B;j++)
			if(vis[0][j][i])
			{
				cout<<i<<" ";
			}
	 
	return 0;
} 

2、走迷宫(模板)

给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。

最初,有一个人位于左上角 (1,1) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。

请问,该人从左上角移动至右下角 (n,m)处,至少需要移动多少次。

数据保证 (1,1)处和 (n,m) 处的数字为 0,且一定至少存在一条通路。

输入格式

第一行包含两个整数 n 和 m。

接下来 n 行,每行包含 m个整数(0 或 1),表示完整的二维数组迷宫。

输出格式

输出一个整数,表示从左上角移动至右下角的最少移动次数。

数据范围

1≤n,m≤100

输入样例:
5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
输出样例:
8
思路:

经典的广度优先搜索模板,不过这次vis数组不仅用来记录是否访问,还用来记录步数

代码:
#include<bits/stdc++.h>

using namespace std;

const int N=103;

int n,m;

typedef pair<int,int> PII; 

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

int g[N][N];
int vis[N][N];

int hh=0,tt=0;
PII q[N*N];

void bfs()
{
	q[0]={1,1};
	while(hh<=tt)
	{
		PII t=q[hh++];//pop
		for(int i=0;i<4;i++)
		{
			int nx=t.first+dx[i];	
			int ny=t.second+dy[i];
			if(nx>=1 && ny>=1 && nx<=n && ny<=m && vis[nx][ny]==-1 && g[nx][ny]==0)
			{
				vis[nx][ny]=vis[t.first][t.second]+1;
				q[++tt]={nx,ny};//enqueue
			}
		}	
	}	
	
}

int main()
{
	cin>>n>>m;
	//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	//千万别忘了memset
	memset(vis,-1,sizeof vis);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&g[i][j]);
		}//read graph
	
	bfs(); 
	
	cout<<vis[n][m]+1;
	
	return 0;	
} 
/*
5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0

8

*/

3、八数码1(模板)

在一个 3×3的网格中,1∼8 这 8 个数字和一个 x 恰好不重不漏地分布在这 3×3的网格中。

例如:

1 2 3
x 4 6
7 5 8

在游戏过程中,可以把 x 与其上、下、左、右四个方向之一的数字交换(如果存在)。

我们的目的是通过交换,使得网格变为如下排列(称为正确排列):

1 2 3
4 5 6
7 8 x

例如,示例中图形就可以通过让 x 先后与右、下、右三个方向的数字交换成功得到正确排列。

交换过程如下:

1 2 3   1 2 3   1 2 3   1 2 3
x 4 6   4 x 6   4 5 6   4 5 6
7 5 8   7 5 8   7 x 8   7 8 x

现在,给你一个初始网格,请你求出得到正确排列至少需要进行多少次交换。

输入格式

输入占一行,将 3×3 的初始网格描绘出来。

例如,如果初始网格如下所示:

1 2 3 
x 4 6 
7 5 8 

则输入为:1 2 3 x 4 6 7 5 8

输出格式

输出占一行,包含一个整数,表示最少交换次数。

如果不存在解决方案,则输出 −1。

输入样例:
2 3 4 1 5 x 7 6 8
输出样例
19
思路:

数组中x的位置索引k可以转化出x在二维数组中的坐标:

x=k/3,y=k%3

代码:
#include<bits/stdc++.h>

using namespace std;

string start;

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

//2 3 4 1 5 x 7 6 8

//19
 
int bfs(string start)
{
	//if(start==end)return d[] 不小心写递归了
	string end="12345678x";
	
	unordered_map<string,int>d;
	
	d[start]=0;//初始步数为0;
	
	queue<string>q;
	q.push(start);
	
	while(q.size())
	{
		string t=q.front();
		q.pop();
		
		int distance=d[t];
		//int cnt1=0;
		//for(int i=0;i<3;i++)
		//{
			//for(int j=0;j<3;j++)
			//{
				//cout<<t[cnt1++]<<" ";
			//}
			//cout<<endl;
		//}
		//cout<<endl;
		//cout<<d[t]<<endl;
		if(t==end)return distance;
		
		int k=t.find('x');
		
		int x=k/3;
		int y=k%3;//转化出'x'的位置 
		
		for(int i=0;i<4;i++)
		{

			
			int nx=x+dx[i];
			int ny=y+dy[i];
			//cout<<dx[i]<<" "<<dy[i]<<endl;
			//cout<<nx<<" "<<ny<<endl;
			if(nx>=0 && nx<=2 && ny>=0 && ny<=2  )
			{
				//cout<<k<<" "<<nx*3+y<<endl;
				swap(t[k],t[nx*3+ny]);
				//cout<<"new"<<endl; 

				if(!d.count(t))
				{	
					d[t]=distance+1;
					q.push(t);
				}

				swap(t[k],t[nx*3+ny]);
			}	
		}
		//cout<<-1;
	} 
	return -1;
	
}

int main()
{
	string start="";
	
	for(int i=0;i<9;i++)
	{
		char c;
		cin>>c;
		start+=c;
	}

	cout<<bfs(start);
		
	return 0;	
} 

4、八数码2(《算法竞赛进阶指南》& HDU1043)

在一个 3×3 的网格中,1∼8 这 8 个数字和一个 x 恰好不重不漏地分布在这 3×3 的网格中。

例如:

1 2 3
x 4 6
7 5 8

在游戏过程中,可以把 x 与其上、下、左、右四个方向之一的数字交换(如果存在)。

我们的目的是通过交换,使得网格变为如下排列(称为正确排列):

1 2 3
4 5 6
7 8 x

例如,示例中图形就可以通过让 x 先后与右、下、右三个方向的数字交换成功得到正确排列。

交换过程如下:

1 2 3   1 2 3   1 2 3   1 2 3
x 4 6   4 x 6   4 5 6   4 5 6
7 5 8   7 5 8   7 x 8   7 8 x

把 x 与上下左右方向数字交换的行动记录为 udlr

现在,给你一个初始网格,请你通过最少的移动次数,得到正确排列。

输入格式

输入占一行,将 3×3 的初始网格描绘出来。

例如,如果初始网格如下所示:

1 2 3 
x 4 6 
7 5 8 

则输入为:1 2 3 x 4 6 7 5 8

输出格式

输出占一行,包含一个字符串,表示得到正确排列的完整行动记录。

如果答案不唯一,输出任意一种合法方案即可。

如果不存在解决方案,则输出 unsolvable

输入样例:
2 3 4 1 5 x 7 6 8
输出样例
ullddrurdllurdruldr
思路:

升级版的八数码1,这次新增两个表即可:用哈希表pre来记录状态转移,再开一个哈希表记录每个状态对应的步数即可

代码:
#include<bits/stdc++.h>

using namespace std;

string s;

unordered_map<string,string>pre;

unordered_map<string,char>h;

char oper[]="udlr";
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};

unordered_set<string>st;

queue<string>q;
//2 3 4 1 5 x 7 6 8
//1 2 3 4 5 6 x 7 8 
//ullddrurdllurdruldr
// u d l r
bool bfs()
{
	string end="12345678x";
	q.push(s);
	st.insert(s);
	while(q.size())
	{
		auto t=q.front();
		if(t==end)return true;
		q.pop();
		auto cur=t;
		int k=t.find('x');
		int x=k/3;
		int y=k%3;

		for(int i=0;i<4;i++)
		{
			int nx=x+dx[i];
			int ny=y+dy[i];
			swap(t[k],t[nx*3+ny]);
			if(nx>=0 && ny>=0 && nx<=2 && ny<=2 && pre.find(t)==pre.end())
			{
				q.push(t);
				pre[t]=cur;
				h[t]=oper[i];
			}
				
			swap(t[k],t[nx*3+ny]);
		}
	}
	return false;
}


int main()
{
	string res;
	for(int i=0;i<9;i++)
	{
		char c;
		cin>>c;
		s+=c;	
	} 
	
	if(!bfs())cout<<"unsolvable";
	else
	{
		string t="12345678x";
		while(true)
		{

			if(t==s)
			{
				break;
			}
			res=h[t]+res;
			t=pre[t];
		}

	}
	cout<<res;
	return 0;
}

5、全球变暖(第九届蓝桥杯省赛C++ & JAVA  A组/B组)

你有一张某海域 N×N 像素的照片,”.”表示海洋、”#”表示陆地,如下所示:

.......
.##....
.##....
....##.
..####.
...###.
.......

其中”上下左右”四个方向上连在一起的一片陆地组成一座岛屿,例如上图就有 2 座岛屿。

由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。

具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。

例如上图中的海域未来会变成如下样子:

.......
.......
.......
.......
....#..
.......
.......

请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。

输入格式

第一行包含一个整数N。

以下 N 行 N 列,包含一个由字符”#”和”.”构成的 N×N字符矩阵,代表一张海域照片,”#”表示陆地,”.”表示海洋。

照片保证第 1 行、第 1 列、第 N 行、第 N 列的像素都是海洋。

输出格式

一个整数表示答案。

数据范围

1≤N≤1000

输入样例1:
7
.......
.##....
.##....
....##.
..####.
...###.
.......
输出样例1:
1
输入样例2:
9
.........
.##.##...
.#####...
.##.##...
.........
.##.#....
.#.###...
.#..#....
.........
输出样例2:
1
思路:

遍历图,遇到岛屿开始搜索,广度优先搜索"#"的上下左右是否有".",如果有则标记为淹没,如果旁边有岛屿像素则都属于一个岛,这个岛屿像素要入队,且标记为访问过("."不做处理以便于这个"."来淹没周围的岛屿),如果这个岛最后还有没有被淹没的单位则这个岛存活,最后用原来的总数减去存活岛屿数量即可

代码:
#include<bits/stdc++.h> 
#define x first
#define y second
using namespace std;
char arr[1010][1010];
bool finddao[1010][1010];
int N;
bool bfs(pair<int,int>s1){
    int sum=0;//计算不被海水侵蚀的路地块个数
    bool mark;//用于判断是否被侵蚀
    int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};//初始化偏移量
    queue<pair<int,int>>dui;
    dui.push(s1);
    while(dui.size()){
        auto s2=dui.front();
        dui.pop();
        mark=1;
        for(int i=0;i<4;i++){
            int x1=s2.x+dx[i],y1=s2.y+dy[i];
            if(x1<0||x1>=N||y1<0||y1>=N)continue;
            if(arr[x1][y1]=='.'){mark=0;continue;}
            if(finddao[x1][y1])continue;
                finddao[x1][y1]=true;
                dui.push(make_pair(x1,y1));
        }
        if(mark==1)sum++;
    }
    if(sum>=1)//如果岛上有不被侵蚀的路地块那么这岛能存活
    return 1;
    else return 0;
}
int main(){
    int allisland=0,lifeisland=0;
    pair<int,int> s1;
    cin>>N;
    for(int i=0;i<N;i++)cin>>arr[i];
    for(int i=0;i<N;i++){
        for(int j=0;j<N;j++){
            if(arr[i][j]=='#'&&!finddao[i][j]){
                s1={i,j};
                allisland++;//计算所有岛的数目
                lifeisland+=bfs(s1);//计算能存活的岛
            }
        }
    }
    cout<<allisland-lifeisland;//用所有的岛数目减能留下的岛数目剩下就是沉下的岛
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值