本周主要学习BFS入门
1、
题目:洛谷 P1135 奇怪的电梯
呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 i 层楼(1≤i≤N)上有一个数字Ki(0≤Ki≤N)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如:3,3,1,2,5 代表了 Ki(K1=3,K2=3,……),从 1 楼开始。在 1 楼,按“上”可以到 4 楼,按“下”是不起作用的,因为没有 −2 楼。那么,从 A 楼到 B 楼至少要按几次按钮呢?
思路:
这道题中一个楼层只能访问一次(第二次访问同一楼层时,步数肯定比第一次的多,直接舍弃),先用结构体把楼层和步数绑定,构建函数,以及两个结构体类型的变量,一个存当前的楼层和步数,一个存下一个楼层和步数,把当前的放入队列中,然后判断是不是目标楼层,是的话直接输出,如果不是,把当前的这个pop出去,然后再把当前楼层上下移动的楼层push到队列中,循环下去,代码全是注释,很详细
# include <bits/stdc++.h>
using namespace std;
int N,Start,End; //总楼层,起始,目标
int a[202];//存储每层楼上的数字
int vis[202];//记录楼层是否被访问
struct pos
{ int level;//当前所在楼层
int steps;//到达该楼层的步数
};
void bfs();
int main()
{
while(scanf("%d",&N)==1)//读取N直到不是整数
{
if(N==0) break;
scanf("%d%d",&Start,&End);
for(int i=1;i<=N;i++)
{
scanf("%d",&a[i]);
vis[i]=0;//未访问时为0
}
bfs();
getchar();
}
return 0;
}
void bfs()
{
pos cur,nex;//声明了两个pos类型的变量,cur是当前,nex是下一个
cur.level =Start;//初始化,起始楼层时start
cur.steps =0;//起始步数为0
queue<pos>qu;//声明pos队列
qu.push(cur);//将初始状态放入队列
vis[Start]=1;//start这一楼层已经访问过了
while(!qu.empty())//队列不为空时执行循环体
{
cur=qu.front();//取出队首元素表示当前状态
qu.pop();//取出队首元素表示当前状态
if(cur.level == End)//如果当前楼层为目标楼层
{
printf("%d\n",cur.steps );//输出步数
return;//返回,程序结束
}
nex.level =cur.level+a[cur.level];//计算向上移动时的下一个状态
nex.steps =cur.steps + 1;//步数加一
if(nex.level <= N)//如果下一楼没超过最高楼
{
if(vis[nex.level]==0)//如果下一楼还没来过
{
vis[nex.level]=1;//标记一下,说明来过
qu.push(nex);//加入队列
}
}
nex.level =cur.level -a[cur.level ];
nex.steps =cur.steps +1;
if(nex.level >= 1)
{
if(vis[nex.level]==0)
{
vis[nex.level]=1;
qu.push(nex);
}
}
}
printf("-1\n");
return;
}
2、
题目:洛谷 P1443 马的遍历
有一个 n×m 的棋盘,在某个点(x,y) 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。
思路:
和上一题类似,上一题是电梯要么向上要么向下,这一题时马有八个方向,因此不能向上题一样写一遍向上,再写一遍向下,这次用一个数组储存移动后改变的坐标,然后在BFS函数中加一个for循环即可
#include <bits/stdc++.h>
using namespace std;
int n, m, Start1, Start2;
int num[404][404] = {-1};//将num数组初始化为1
int pro[8][2] = {1, 2, -1, 2, 1, -2, -1, -2, 2, 1, 2, -1, -2, 1, -2, -1};
//马可能跑8个位置
struct point {
int level1, level2;//记录点位
};
struct pos {
point p;
int steps;//记录步数
};
void bfs(int start1, int start2) {
int vis[404][404] = {0};//记录是否访问
pos cur, nex;//cur当前,nex下一个
cur.p.level1 = start1;//记录当前位置
cur.p.level2 = start2;//记录当前位置
cur.steps = 0;//当前步数为0
queue<pos> qu;//定义一个pos类型名叫qu的队列
qu.push(cur);//将当前的信息放入队列
vis[start1][start2] = 1;//起点已经被访问过
while (!qu.empty()) {//队列不为空时一直循环
cur = qu.front();//将当前的状态更新为队列第一个
qu.pop();//弹出队列第一个
for (int i = 0; i < 8; i++) {//马可能向八个位置跑
nex.p.level1 = cur.p.level1 + pro[i][0];//更新下一个位置
nex.p.level2 = cur.p.level2 + pro[i][1];//更新下一个位置
nex.steps = cur.steps + 1;//步数加1
/*判断越界否*/if (nex.p.level1 <= n && nex.p.level2 <= m && nex.p.level1 >= 1 && nex.p.level2 >= 1) {
if (vis[nex.p.level1][nex.p.level2] == 0) {//判断是否访问过
vis[nex.p.level1][nex.p.level2] = 1;//更新为已访问
num[nex.p.level1][nex.p.level2] = nex.steps;//更新下一个位置的步数(前面已求出)
qu.push(nex);//把下一个放进队列,目的是让它成为当前状态
}
}
}
}
}
int main() {
scanf("%d %d %d %d", &n, &m, &Start1, &Start2);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
num[i][j] = -1;//初始化为-1
}
}
num[Start1][Start2] = 0;//起点步数为0
bfs(Start1, Start2);
for (int a = 1; a <= n; a++) {
for (int b = 1; b <= m; b++) {
printf("%d ", num[a][b]);
}
printf("\n");
}
return 0;
}
3、
题目:洛谷 P3958 [NOIP2017 提高组] 奶酪
现有一块大奶酪,它的高度为 ℎh,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多半径相同的球形空洞。我们可以在这块奶酪中建立空间坐标系,在坐标系中,奶酪的下表面为 z=0,奶酪的上表面为 z=h。
现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交,Jerry 则可以从奶酪下表面跑进空洞;如果一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。
位于奶酪下表面的 Jerry 想知道,在不破坏奶酪的情况下,能否利用已有的空洞跑 到奶酪的上表面去?
空间内两点 P1(x1,y1,z1)、P2(x2,y2,z2) 的距离公式如下:
dist(P1,P2)=sqrt【(x1−x2)^2+(y1−y2)^2+(z1−z2)^2】
思路:
判断两圆是否相交或相切,就是两圆心距离和直径的比较,直接搜索,这道题先判断某点是否到下界,如果到,加入队列,之后判断上界,需要用long long
#include <bits/stdc++.h>
using namespace std;
long long t, n, h, r, d;
bool ok, vis[1009];
long long x[1009], y[1009], z[1009];
long long dist(long long a1, long long a2, long long a3, long long b1, long long b2, long long b3)
{
return pow(a1 - b1, 2) + pow(a2 - b2, 2) + pow(a3 - b3, 2);
}
bool bfs(long long n, long long h, long long r)
{
queue<int> q;
for (int i = 1; i <= n; i++)
{
if (z[i] + r >= 0 && z[i] - r <= 0)
{
vis[i] = 1;
q.push(i);
}
}
long long di, dx, dy, dz;
while (!q.empty())
{
int k = q.front();
q.pop();
if (r + z[k] >= h)
{
ok = 1;
break;
}
for (int i = 1; i <= n; i++)
{
if (vis[i])
continue;
di = dist(x[k], y[k], z[k], x[i], y[i], z[i]);
if (di<=d*d)
{
vis[i] = 1;
q.push(i);
}
}
}
for (int i = 1; i <= n; ++i)
vis[i] = 0;
if (ok)
{
ok = 0;
return true;
}
else
{
return false;
}
}
int main()
{
scanf("%lld", &t);
for (int a = 0; a < t; a++)
{
scanf("%lld %lld %lld", &n, &h, &r);
d = 2 * r;
for (int b = 1; b <= n; b++)
{
scanf("%lld %lld %lld", &x[b], &y[b], &z[b]);
}
if (bfs(n, h, r))
{
printf("Yes\n");
}
else
{
printf("No\n");
}
}
return 0;
}
4、
题目:洛谷 P1162 填涂颜色
由数字 0 组成的方阵中,有一任意形状闭合圈,闭合圈由数字 1 构成,围圈时只走上下左右 4 个方向。现要求把闭合圈内的所有空间都填写成 2。例如:6×6 的方阵(n=6),涂色前和涂色后的方阵如下:
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 0 0 1
1 1 0 0 0 1
1 0 0 0 0 1
1 1 1 1 1 1
0 0 0 0 0 0
0 0 1 1 1 1
0 1 1 2 2 1
1 1 2 2 2 1
1 2 2 2 2 1
1 1 1 1 1 1
思路:
本题可以不用bfs,先遍历让所有0都变成2,然后把最边上一圈的2全变成0(最边上的0不可能被1包围),然后能够和0挨着的2全变成0(从右下向左上遍历),最后输出即可
# include<bits/stdc++.h>
using namespace std;
int n,m=0;
int num[33][33]={0};
int dm[33]={0};
int main()
{
scanf("%d",&n);
for(int a=1;a<=n;a++)
{
for(int b=1;b<=n;b++)
{
scanf("%d",&num[a][b]);
if(num[a][b]==0&&a!=1&&b!=1&&a!=n&&b!=n)
num[a][b]=2;
}
}
for(int a=2;a<=n;a++)
{
for(int b=2;b<=n;b++)
{
if(num[a][b]!=1)
if(num[a-1][b]==0||num[a+1][b]==0||num[a][b-1]==0||num[a][b+1]==0)
{
num[a][b]=0;
}
}
}
for(int a=n;a>=2;a--)
{
for(int b=n;b>=2;b--)
{
if(num[a][b]!=1)
if(num[a-1][b]==0||num[a+1][b]==0||num[a][b-1]==0||num[a][b+1]==0)
{
num[a][b]=0;
}
}
}
for(int a=1;a<=n;a++)
{
for(int b=1;b<=n;b++)
{
printf("%d ",num[a][b]);
}
printf("\n");
}
return 0;
}