问题 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的朋友在右下角。
输入
输入一组数据
每组数组为55的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: 红红去小寨
题目描述
红红今天中午想去市中心吃饭,他决定坐公交车去。但是去公交车的时候红红决定练习一下空间魔法,穿梭时空
现在红红到公交车的路是一条直线,然后他可以通过三种方式移动:
- 向前走一个位置
- 向后走一个位置
- 空间移动,将自己的位置从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;
}