搜索 dfs+bfs

问题 A: 俊俊家里有矿(搜索例题) 连通块

题目描述
大家都知道俊俊家有矿,但是这也使俊俊特别烦恼,因为俊俊老爸每天都会问俊俊,家里还有多少块油田。俊俊家里的
油田看起来就像一个m行n列的字符矩阵,由字符‘@’ 和 ‘” 组成。’@‘代表油田,如果两个字符’@'相邻,就说明他们属于
同一块油田。两个字符相邻当且仅当两个字符的位置关系为横,竖,对角线。你能告诉俊俊他家有多少块油田。
输入
输入多组数据,
第一行输入两个数字m n。 1 < m n< 100。
接下来输入m行n列的字符矩阵。
输入0 0 代表结束。
输出
输出一个数字代表俊俊家有多少块油田。
样例输入
1 1
*
3 5
@@

@
@@*
1 8
@@***@
5 5
****@
@@@
@**@
@@@
@
@@**@
0 0
样例输出
0
1
2
2


//使用dfs 递归所有状态 
#include<cstdio>
#include<cstring>
#include<iostream>
const int maxn=100+8;
using namespace std;
char oil[maxn][maxn];
int vis[maxn][maxn];
int cnt;
int n,m;
void dfs(int x,int y){
    if(x<0||x>=n||y<0||y>=m) return; //出界
    if(oil[x][y]!='@'||vis[x][y]) return;  //不是油田 或者已经被标记过了
    vis[x][y]=1;  //标记
    for(int i=-1;i<=1;i++)  // 两层循环 控制上下左右移动 
      for(int j=-1;j<=1;j++)
      if(i||j)  //i=0,j=0时不移动,所以排除这种移动
      dfs(x+i,y+j);
} 
int main(){
    while(scanf("%d %d",&n,&m)&&(n||m)){
        memset(vis,0,sizeof(vis));
        cnt=0;
        for(int i=0;i<n;i++)
          scanf("%s",oil[i]);
        for(int i=0;i<n;i++)
           for(int j=0;j<m;j++)  //两个for 遍历整个棋盘
              if(oil[i][j]=='@'&&!vis[i][j]){  
                dfs(i,j); //如果他是油田并且没有被标记过,进入这块油田
                cnt++;  //这块油田走完了,他的数量加1
              }
                
      printf("%d\n",cnt);  //打印数量
                
    }
    return 0;
}

问题 B: 路痴的XX(搜索例题)BFS + 路径打印
题目描述
XX是个爱玩的女孩纸,而且喜欢逛街。逛街的时候,因为痴迷于商场里面的商品,所以XX经常和朋友走丢,每次走丢XX还非常自信给朋友打电话,让朋友告诉XX位置,自己过去找她。但是XX是一个路痴,除非告诉她每一步怎么走,不然XX可能找不到朋友XX已经逛街逛到腿软,所以她想在最短的时间内找到朋友,每走一步花的时间一样多,XX只能向上、下、左、右走。你能帮助XX找到她的朋友吗。商场地图可以看成一个55的二维数组,XX在左上角,XX的朋友在右下角。
输入
输入一组数据
每组数组为5
5的01数字矩阵,输入数据保证有唯一解。
输出
XX从左上角到右下角的最短路径。
样例输入
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
样例输出
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#include<stack>
using namespace std;
const int maxn=5;
int map[maxn][maxn];
int vis[maxn][maxn];//标记数组
int step[maxn][maxn];//路径数组
int x[4]={-1,1,0,0};
int y[4]={0,0,-1,1};   //移动数组  x和y上下对应 形成相应的移动方向
 
struct node {
  /* data */
  int x;
  int y;
  int cnt;
  node(int x=0,int y=0,int cnt=0){  //用构造器初始化
    this->x=x;
    this->y=y;
    this->cnt=cnt;
  }
};
 node fa[maxn][maxn]; //父亲结点数组 值为横坐标和列坐标的父亲结点
 
 int check(int x,int y){  //检查函数
   if(x<0||x>4||y<0||y>4) return 0; //出界 返回0
   if(vis[x][y]||map[x][y]==1) return 0;  //如果已经被标记或者是障碍 返回0
    return 1;
 }
 
void bfs(){
  memset(vis,0,sizeof(vis));
  queue<node> q;
  node now;  //定义现在结点
  now.x=0,now.y=0,now.cnt=0;  // 左上角的初始化
  q.push(now);//起点入队列
  while(!q.empty()){  //队列不为空时执行搜索
     now=q.front();  //取现在队列的头
     q.pop();//将这个结点出队
     vis[now.x][now.y]=1;//标记这个结点已经被访问过了
     step[now.x][now.y]=now.cnt; //标记此时的步数
     if(now.x==4&&now.y==4) return;  //当走到终点时结束
      //当做完结点标记和判定时就进行移动操作
      //回溯走四次
     for(int i=0;i<4;i++){
         node next;  //定义下一个结点
         next.x=now.x+x[i];
         next.y=now.y+y[i];
         if(check(next.x,next.y)){  //如果下一个结点可以被访问
           next.cnt=now.cnt+1;  //路径+1;
           q.push(next);
           fa[next.x][next.y]=now; //next的父节点是now
         }
     } //执行for
  } //执行while   一直执行到队列为空或者到终点
  return ;
}
void print_path(node u){
    stack<node> s;  //栈
    while(1){
      s.push(u);
      if(step[u.x][u.y]==0) break;  //当路径为0时,说明回到起点
        u=fa[u.x][u.y];   //回溯到父亲结点  找父结点
    }
    while(!s.empty()){
      printf("(%d, %d)\n",(s.top()).x,(s.top()).y);
      s.pop();
    }
}
 
int main(){
     for(int i=0;i<5;i++)
       for(int j=0;j<5;j++)
          scanf("%d",&map[i][j]);
    bfs();
    node ans(4,4,step[4][4]);  //构造器 传入终点结点坐标和步数(step[4][4])
    print_path(ans); 
    return 0;
}

问题 C: “下班啦,打卡成功!”

题目描述
参加集训的同学下班都要打卡,现在我们决定根据打卡时间让最后离开的同学关一下教室的门。
每个同学都有自己打卡时间和独一无二的学号,请找出负责关门的同学
输入
多组测试输入,每组输入第一行为一个正整数 n 表示参加集训的人数, 1 <= n <= 200。
接下来 n 行,每行为一个长度不超过 15 的字符串表示同学的学号,和一个时间信息表示打卡时间。
保证时间信息为同一天并且为 24 小时制
输出
输出负责关门的同学的学号。
若两个同学打卡时间相同,则让学号字典序小的同学关门。
样例输入
5
2017000013 16:00
2018000052 16:30
201705123 17:00
201745464 15:30
201821366 18:25
2
2017111111 16:00
2018132111 16:00
样例输出
201821366
2017111111


     本题是前几天的知识,定义一个结构体然后排序 
 //优先队列的方法    
#include<cstdio>
#include<bits/stdc++.h>
#include<cstring>
#include<queue>
using namespace std;
 
struct student{
  string number;
  int time;
}temp;
 
bool operator < (student a,student b){   //重载运算符
  if(a.time<b.time) return 1;  //时间优先
  else if(a.time==b.time&&(a.number>b.number)) return 1;  //时间一样则学号优先
  return 0;
}
 
int main(){
  int n;
  while(scanf("%d",&n)!=EOF){
  priority_queue<student> q;   //定义优先队列
  while(n--){
    string h;
    int shi,fen,t;
    cin>>h;
    scanf("%d:%d",&shi,&fen);
    t=shi*60+fen;  //时间转换为整数
    temp.number=h;
    temp.time=t;
    q.push(temp);  //入队列
  }
  cout<<(q.top()).number<<endl;
}
  return 0;
}
 直接进行排序
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 200+8;
struct student{
	string s;
	int time;
};
student st[maxn];  //定义结构体数组
int n;
bool cmp(student a, student b)  //定义比较函数
{
	if(a.time > b.time || (a.time == b.time && a.s < b.s)) return 1;
	return 0;
}
int main()
{
	int h, m;
	while(scanf("%d", &n) == 1){
		for(int i = 0; i < n; i++){
			cin >> st[i].s;
			scanf("%d:%d", &h, &m);
			st[i].time = h*60 + m;
		}
		sort(st, st+n, cmp);  //使用sort
		cout << st[0].s << endl;
 	}
	return 0;

问题 D: 红红去小寨

题目描述
红红今天中午想去市中心吃饭,他决定坐公交车去。但是去公交车的时候红红决定练习一下空间魔法,穿梭时空
现在红红到公交车的路是一条直线,然后他可以通过三种方式移动:

  1. 向前走一个位置
  2. 向后走一个位置
  3. 空间移动,将自己的位置从x 移动到 2 * x
    三种移动方式都需要 10 s
    但是红红希望能尽量减少体力的消耗, 所以他希望能以最短的时间到达公交车站
    输入
    有多组测试样例
    每组测试样例包括一个 n 和 k ( 0 < n k<= 100000)
    n 表示红红目前的位置
    k 表示公交车站的位置
    输出
    输出公交车站所需要的最小用时,输出 x:x (表示几分几秒)
    样例输入
    5 17
    7 4
    5 10000
    样例输出
    0:40
    0:30
    2:10
    提示
    N 可能大于 K

  路径最短,时间最短,所以使用bfs求最短路径,最后乘上每一步的时间
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
#define ll long long
#define N 100005
using namespace std;
int vis[N],dis[N];  vis数组用来标记 dis用来记录步数
int ans=1;
int n,k;
queue<int>Q;
void bfs(int rt)
{
    vis[rt]=1;  //rt为人的初始坐标
    Q.push(rt);  //坐标入队列
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop();
        int to;  //定义下次移动坐标to
        for(int i = 1;i <= 3;i++)
        {
            if(i==1) to=u+1;   //to有三种不同的情况,用for控制
            else if(i==2) to=u-1;
            else if(i==3) to=u*2;
            if(to<0||to>N) continue;  //如果超过公交车,或者小于0,重新试移动步数
            if(!vis[to])  //如果未被标记
            {
                vis[to]=1;  //标记此次坐标
                Q.push(to); //入队列
                dis[to]=dis[u]+1; //移动次数加一
            }
            if(to==k)  //当到达公交车的坐标时,则说明完成
            {
                ans=dis[to];  //赋值此时的移动步数
            }
        }
    }
}
int main()
{
while(scanf("%d%d",&n,&k)!=EOF){
  memset(dis,0,sizeof(dis));
  memset(vis,0,sizeof(vis));
    if(n>=k)  //因为时线性的,2*x只能向前,所以当他在公交车之前,就只能一步一步往回倒
    {
    int t1=n-k;  //
        printf("%d:%d\n",t1*10/60,t1*10%60);
    }else
    {
        bfs(n);  //否则广度搜素
    int t=ans;
        printf("%d:%d\n",t*10/60,t*10%60);
    }
}
    return 0 ;
}
 

问题 E: 奇怪的电梯

题目描述
有栋个N层没有地下室的楼,这栋楼有一个奇怪的电梯,
这个电梯里面只有两个按钮,“UP” 或者 “DOWN”
当你在i层时,你只能到i+ki或者i-ki层
你想从A层去B层
请输出你最少要按多少次按钮
输入
输入多组样例
第一行包含三个整数N,A,B(1 <= N,A,B <= 200),如上所述,第二行包括N个整数k1,k2,… kn。
单个0表示输入结束。
输出
入输出的每个情况一个整数,表示从A层去B层最少要按多少次按钮。
如果你不能到达B楼,则打印“-1”。
样例输入
5 1 5
3 3 1 2 5
0
样例输出
3

最少按多少次,还是求最短路径,所以还是用bfs
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
#define ll long long
#define N 100005
using namespace std;
int mp[200],k[200];  
//mp存的是当前层数的走的步数  k的下标为层,值为此层能上或者下的层数
int n,a,b;
queue<int> q;
int bfs(){
  q.push(a);
  while(!q.empty()){
    int m=q.front();
    q.pop();
    if(m==b) return mp[b]-1;  //如果到达指定层数则说明完成广度搜索,打印此层的步数,因为刚开始第一次的步数定义的步数是1,所以需要-1
    if( m+k[m]<=n && mp[m+k[m]]==0){ //往上走要判定是不是大于第一层
     //mp既是步数也可以用来判定,如果步数为0,则说明还没走过
      mp[m+k[m]]=mp[m]+1; //步数+1;
      q.push(m+k[m]); //入队列
    }
    if(m-k[m]>=1 && mp[m-k[m]]==0){ //往下走要判定是不是小于第一层
       mp[m-k[m]]=mp[m]+1;//步数+1
       q.push(m-k[m]); //入队列
    }
  }
  return -1;
}
int main()
{
while(scanf("%d",&n)&&n){
scanf("%d %d", &a, &b);
  memset(mp,0,sizeof(mp));  //初始化mp
  memset(k,0,sizeof(k));  //初始化k
  for(int i=1;i<=n;i++){
    scanf("%d",&k[i]);
  }
   mp[a]=1; 
   //第一次的步数为1,因为之后判定时需要用他来判定是否被访问过,所以第一次的步数不能为0,否则之后搜索就会让第一次的层数再被访问
     cout<<bfs()<<endl;
}
    return 0 ;
}

问题 F: 肥宅快乐水(搜索例题)倒水问题

题目描述
红红是个肥宅,所以他很爱喝肥宅快乐水。但是每次当红红买了肥宅快乐水后,左左都要求和红红一起分享这一瓶可乐,而且要和红红喝得一样多。但是红红手中只有两个杯子,他们的容量的分别是 N 毫升 和 M毫升,可乐的体积为 S 毫升(正好装满一升)。他们三个之间可以相互倒可乐,都是没有刻度的,而且 S == N+M,0 < S < 101 0 < N 0 < M。聪明的你们能告诉他们能平分吗,如果能请输出倒可乐的最少次数,如果不能输出”NO“。
输入
三个整数: S 可乐的体积, N 和 M 是两个杯子的容量,以 ”0 0 0”结束。
输出
如果能输出倒可乐的最少次数,如果不能输出“NO”。
样例输入
7 4 3
4 1 3
0 0 0
样例输出
NO
3

#include<bits/stdc++.h>
#include<queue>
using namespace std;
const int maxn=100+1;
  int v[5]; //v[1] v[2] v[3]  用来记录三个杯子的体积
int vis[maxn][maxn][maxn];   //标记数组 用来标记是否被访问过
 
struct cup{   //用来记录状态
    int v[5];    //v[1] V[2] V[3]用来记录导入之后的水
    int cnt;  //记录步数
  }temp;
 
void pour(int a,int b){  //杯子a倒向杯子b
  int sum=temp.v[a]+temp.v[b];  //如果
  if(sum>=v[b]){ //如果倒入后大于了b被子的体积
     temp.v[b]=v[b]; //则此时b杯子的体积等于b的容积
  } else{
    temp.v[b]=sum;
  }
     temp.v[a]=sum-temp.v[b];   //则此时a杯子的体积就变为sun-b杯子的可乐体积
}
 void bfs(){
   queue<cup> q;
    cup cur;
    cur.v[1]=v[1];  //刚开始将可乐的体积赋给可乐杯子
    cur.v[2]=0;
    cur.v[3]=0;
    cur.cnt=0;
    q.push(cur);
    vis[v[1]][0][0]=1; //标记
    while(!q.empty()){ //判断被子是否为空
        cur=q.front();
        q.pop();
        if(cur.v[2]==0&&cur.v[1]==cur.v[3]){ //目标状态
          printf("%d\n",cur.cnt);
          return ;
        }
        for(int i=1;i<=3;i++){  //三个杯子互相倒
          for(int j=1;j<=3;j++){
            if(i!=j){
              temp=cur; //当前状态赋值给temp;
              pour(i,j);
              if(!vis[temp.v[1]][temp.v[2]][temp.v[3]]){  // 判重 这三个杯子各自体积的状态没有出现过
                temp.cnt++;    //步数+1
                 q.push(temp); //入队列
                 vis[temp.v[1]][temp.v[2]][temp.v[3]]=1;  //标记此时状态
              }
            }
          }
        }
    }
    printf("NO\n");  //一直搜索完毕都没有找到最终状态 则输出NO
    return ;
 }
 
 int main(){
   while(scanf("%d %d %d",&v[1],&v[2],&v[3])&&(v[1]||v[2]||v[3])){
     if(v[2]>v[3]){  //最后平分的水一定会最大的两个杯子里面  用于判断最终状态 因为判定最终状态时认定v[1]和v[3]为最大杯子
       int t=v[2];
       v[2]=v[3];
       v[3]=t;
     }
     bfs();
   }
   return 0;
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值