八月五号开始接触了搜索,常见的搜索目前接触到的也就是dfs 个人感觉dfs很不好理解,不好写 ,难点在于深搜,标记,回溯吧。这几天做了一些dfs,bfs的题目,多多少少十多道吧,dfs有数独,找数,棋盘等,bfs就比较多了,大概有最普通的二维,三位迷宫问题,同时对于一些求最短的路径的保存,以及一些特殊问题求最少次数等等这些应该是bfs的题眼了吧,目前接触的bfs中,碰到了个人觉得很恶心的题目,还没有理解的,也有一些好些的题目,但是需要一些技巧性的题目,话不多说了,拉题目,对于题目是需要用dfs搜索,还是bfs搜素还是需要认真审题,从题目本身的目的出发,来找到最好的解题步骤,题目有点多,比较繁琐,完全手模 ;
先从dfs开始:
题目链接:https://vjudge.net/contest/317155#problem/I
这是一道很有意思的dfs题目,刚开始拿到这道题目,想到了八皇后问题,然后就开始想八皇后,仔细看这道题目,并没有八皇后那个题目恶心,因为它只要求行,列不在一起就可以了,但是恶心的是它的大小并不确定,所以当时看到这个题目并没有思路,简单看了一下题解吧,和求八皇后一样,从第一行开始枚举,枚举每一行中每一列的情况,用代码中的vis数组来标记,这个dfs难的地方主要是,它每一行不一定会有棋盘,所以关键代码就是 dfs(i+1) 这一行没有棋盘就递归寻找下一行。
核心代码:
void dfs(int i)//i代表的是行
{
if(sum==k)
{
ans++; return ;
}
if(i>=n) return; //数组的下标从0开始的
for(int j=0;j<n;j++)
{
if(!vis[j]&&mapp[i][j]=='#)//这一列为空并且这个点是棋盘
{
vis[j]=1;//标记
sum++;
dfs(i+1);//深搜
vis[j]=0;//回溯
sum--;
}
}
dfs(i+1); //这一行没有棋盘
}
下面就是完整的代码了:
#include <iostream>
#include <cstring>
using namespace std;
int vis[1005];
char mapp[10][10];
int n,k,sum,ans;
void dfs(int i)//行开始扫
{
if(sum==k)
{
ans++; return ;
}
if(i>=n) return ;
for(int j=0;j<n;j++)//这是每一列
{
if(!vis[j]&&mapp[i][j]=='#')
{
vis[j]=1;
sum++;
dfs(i+1);
vis[j]=0;//满足了,或者不满足都要回溯
sum--;
}
}
dfs(i+1); //这一行可能没有棋盘
}
int main()
{
while(scanf("%d %d",&n,&k))
{
if(n==-1&&k==-1) break;
getchar();
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
scanf("%c",&mapp[i][j]);
}
getchar();
}
ans=0,sum=0;
dfs(0);//从第0行开始扫
printf("%d\n",ans);
}
return 0;
}
第二道dfs;
E - 魔法
在一个有魔法的世界里,每个孩子都会魔法,老师今天布置的作业是把石头变成猪。
在那个世界里每个符号代表一种东西,例如,b代表石头,m代表猪。
用只有小写字母的字符串表示魔法
比如
一个魔法abcde会使a变为e
魔法cdas会使c变s
也就是每一个魔法,会使首字母代表的东西变成尾字母代表的东西
Input
输入你会的魔法,每行一个,以0结束
有多组输入数据
Output
如果你能做完作业,就输出"Yes.",否则就输出"No."(不要忽略了句号)
作业的内容是把: b(代表石头)变成m(猪)
对于每组输入数据,每个输出一行
Sample Input
so
soon
river
goes
them
got
moon
begin
big
0
Sample Output
Yes.
题目:题目很好理解,就是给你几个字符串,每个字符串它的第一位对应于它的最后一位;问在几次循环后能将b和m对应上;
思路:用结构体来储存串的开头和结尾,这个简单,对于字符串的处理可以用一个 if,else来处理一下同时也是比较好理解吧,然后对于字符串进行搜索,匹配,dfs即可,贴代码。
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int maxn=1e+5;
int vis[maxn];
char s[100];
int k=0,len,flag;
struct node
{
char bin,end;
}q[maxn];
void dfs(int pos)
{
if(q[pos].end=='m')
{
flag=1; return;
}
for(int i=0;i<k;i++)
{
if(q[pos].end==q[i].bin&&!vis[i])
{
vis[i]=1;
dfs(i);
vis[i]=0;
}
}
}
int main()
{
while(scanf("%s",s)!=EOF)
{
if(strcmp(s,"0")!=0)
{
q[k].bin=s[0];
q[k++].end=s[strlen(s)-1];
}
else //过渡
{
flag=0;
for(int i=0;i<k;i++)
{
if(q[i].bin=='b')
dfs(i);
}
if(flag) printf("Yes.\n");
else
printf("No.\n");
k=0;
}
}
return 0;
}
第三道dfs:
G - 油井
GeoSurvComp地质调查公司负责探测地下石油储藏。 GeoSurvComp现在在一块矩形区域探测石油,并把这个大区域分成了很多小块。他们通过专业设备,来分析每个小块中是否蕴藏石油。如果这些蕴藏石油的小方格相邻,那么他们被认为是同一油藏的一部分。在这块矩形区域,可能有很多油藏。你的任务是确定有多少不同的油藏。
Input
输入可能有多个矩形区域(即可能有多组测试)。每个矩形区域的起始行包含m和n,表示行和列的数量,1<=n,m<=100,如果m =0表示输入的结束,接下来是n行,每行m个字符。每个字符对应一个小方格,并且要么是’*’,代表没有油,要么是’@’,表示有油。
Output
对于每一个矩形区域,输出油藏的数量。两个小方格是相邻的,当且仅当他们水平或者垂直或者对角线相邻(即8个方向)。
Sample Input
1 1
*
3 5
@@*
@
@@*
1 8
@@***@
5 5
****@
@@@
@**@
@@@@
@@**@
0 0
Sample Output
0
1
2
2
题目:此题目考察的是dfs求联通块的问题,比较基础,然后注意一点就是在搜到以后把这个点变为
‘*’,避免重复搜索,同时也不需要用一个vis数组来标记,贴代码:
#include <iostream>
using namespace std;
int vis[500][500];
char mapp[500][500];
int n,m,ans,xx,yy;
int dx[8]={1,0,-1,0,1,1,-1,-1};
int dy[8]={0,-1,0,1,-1,1,1,-1};
void dfs(int x,int y)
{
mapp[x][y]='*';
for(int i=0;i<8;i++)
{
xx=x+dx[i],yy=y+dy[i];
if(xx>=0&&xx<n&&yy>=0&&yy<m&&mapp[xx][yy]=='@')
dfs(xx,yy);
}
return ;
}
int main()
{
char c; int bx,by;
while(scanf("%d %d",&n,&m))//n行m个字符
{
ans=0;
if(m==0) break;
getchar();
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
scanf("%c",&mapp[i][j]);
}
getchar();
}
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if(mapp[i][j]=='@')
{
dfs(i,j);
ans++;
}
}
}
printf("%d\n",ans);
}
return 0;
}
第四道dfs:
题目https://vjudge.net/contest/317155#problem/K
给定一个正整数n,请编写一个程序来寻找n的一个非零的倍数m,这个m应当在十进制表示时每一位上只包含0或者1。你可以假定n不大于200且m不多于100位。
提示:本题采用Special Judge,你无需输出所有符合条件的m,你只需要输出任一符合条件的m即可。
Input
输入包含多组数据,每组数据仅一行,只包含一个正整数n (1 <= n <= 200).
Output
对于输入的每组n,都输出任一符合条件的m。即使有多个符合条件的m,你也只需要输出一个即可。
Sample Input
2
6
19
0
Sample Output
10
100100100100100100
111111111111111111
这道题目的关键我认为是数据范围的选取,这里我用unsigned long long 来存取,无符号长整型数它的数据范围可以开到2^64-1 也就是10^19,long long的数据范围是2^63减一就是9e18,同时对于一些dfs题目我发现题目很喜欢用flag来标记,这样写的前提是题目的答案唯一 这样写对于代码是一种优化,在满足条件回溯的时候避免了繁琐的判断 代码如下:
#include <iostream>
using namespace std;
int x,sum,flag;
void dfs(unsigned long long t,int pos)//10^19
{
if(pos>19||flag==1) return ;
if(t%x==0)
{
printf("%lld\n",t);
flag=1;
}
dfs(t*10,pos+1);
dfs(t*10+1,pos+1);
}
int main()
{
while(scanf("%d",&x))
{
if(x==0) break;
flag=0;
dfs(1,1); //第一个1是数,第二个是位数
}
return 0;
}
C - 红与黑
有一个长方形的房间,覆盖了正方形的磁砖。每块磁砖的颜色,要么是红色,要么是黑色。一名男子站在一块黑色的磁砖上。他可以从一块磁砖移至相邻四块磁砖中的某一块。但是,他不允许在红色磁砖上移动,他只允许在黑色磁砖上移动。
编写一个程序,使得他允许重复上述的移动,判断他所能到达的黑色磁砖的数量。
输入
输入由多个数据集组成。数据集的起始行包含了两个正整数 W 和 H;W 和 H 分别是 x- 和 y- 方向的磁砖数量。W 和 H 不超过 20 。
在数据集中,还有 H 行,每行包含了 W 个字符。每个字符按如下方式表示一块磁砖的颜色。
‘.’ - 一块黑色的磁砖
‘#’ - 一块红色的磁砖
‘@’ - 一名男子,站在一块黑色磁砖上 (在一个数据集中,恰好出现一次)
以包含两个 0 的一行,表示输入结束。
输出
对于每个数据集,程序应当输出一行,包含他从初始磁砖所能抵达的磁砖数量 (包括初始磁砖自身)。
示例输入
6 9
…#.
…#
…
…
…
…
…
#@…#
.#…#.
11 9
.#…
.#.#######.
.#.#…#.
.#.#.###.#.
.#.#…@#.#.
.#.#####.#.
.#…#.
.#########.
…
11 6
…#…#…#…
…#…#…#…
…#…#…###
…#…#…#@.
…#…#…#…
…#…#…#…
7 7
…#.#…
…#.#…
###.###
…@…
###.###
…#.#…
…#.#…
0 0
示例输出
45
59
6
13
***题目:***https://vjudge.net/contest/317155#problem/C
思路:这道题目其实和油田是类似题目,属于比较基础的,同样在搜索的过程中,每走一步,答案加一,然后跟新这个点,将其标记的同时,将地图改为’#’;
核心代码:
int dfs(int x,int y)
{
ans++;
mapp[x][y]='#';
for(int i=0;i<4;i++)
{
xx=x+dxx[i];
yy=y+dyy[i];
if(judge(xx,yy))
{
vis[xx][yy]=1;
dfs(xx,yy);
vis[xx][yy]=0;
}
}
return ans;
}
总的代码如下:
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
char mapp[25][25];
int w,h,x,y,dx,dy,ans;
char c;
int vis[25][25];
int dxx[4]={1,-1,0,0};
int dyy[4]={0,0,1,-1};
bool judge(int x,int y)
{
if(x>=0&&x<h&&y>=0&&y<w&&mapp[x][y]=='.'&&!vis[x][y])
return true;
return false;
}
int dfs(int xx,int yy)
{
ans++;
mapp[xx][yy]='#';
for(int i=0;i<4;i++)
{
x=xx+dxx[i];
y=yy+dyy[i];
if(judge(x,y))
{
vis[xx][yy]=1;
dfs(x,y);
vis[xx][yy]=0;
}
}
return ans;
}
int main()
{
while(scanf("%d %d",&w,&h))
{
if(w==0&&h==0) break;
getchar();
for(int i=0;i<h;i++)
{
for(int j=0;j<w;j++)
{
scanf("%c",&mapp[i][j]);
if(mapp[i][j]=='@')
{
dx=i,dy=j;
}
}
getchar();
}
ans=0;
memset(vis,0,sizeof(vis));
ans=dfs(dx,dy);
printf("%d\n",ans);
}
return 0;
}
第五道dfs:题目:https://vjudge.net/contest/317155#problem/F
F - Soduku数独
数独应该是一个大家都玩过的游戏,说的就是在一个99的方格中填入一些数字,符合以下规则:
1.每一列或每一行中1-9只能出现一次。
2.这个数独划分成的9个小的33的方格矩阵内,从1-9的每个数只能出现一次。
Input
输入数据的第一行包括一个整数T,表示有T组测试数据。
每组数据由9行组成,每行由9个整数或者组成,其中表示空白的格子。
Output
每组数据输出9行,每行9个整数,表示整个数独。
每两组输出之间有一个空行。
Sample Input
1
86423
3819*
298
7*952**
6923
1789
327**
6719
15732*
Sample Output
986412537
543678192
172359648
739845261
658921473
421763859
395286714
267134985
814597326
Hint
数据保证答案唯一。 暗示你应该用flag标记,快速回溯!
思路: 这个题目很显然是用dfs来搜索每一个符合的答案,这道题目有一点小技巧,就是输入数据的时候当然是用字符串来做,我们需要一个新的int型二维数组来存下这些数字,方便处理,因为数独都是99一共81个数字,然后我们可以对应到每一个下标,也就是0到80(从下标0开始)然后从第一个去判断,行,列,以及每一个小的33矩阵,是否满足,个人认为这个题目比较难,核心代码:
//判断这个数字是否位置正确 也就是其它题目里的judge 一个意思!
bool ok(int num,int digit)
{
int hang=num/9; int lie=num%9;
for(int i=0;i<9;i++)
{
if(mapp[i][hang]==digit)
return false;
}
for(int i=0;i<9;i++)
{
if(mapp[hang][i]==digit)
return false;
}
int xhang=3*(hang/3); int xlie=3*(lie/3);
for(int i=xhang;i<3+xhang;i++)
{
for(int j=xlie;j<3+xlie;j++)
if(mapp[i][j]==digit)
return false;
}
return true;
}
上边这个代码是表示判断这个数字的位置是否合适的函数,完整函数如下:
#include <iostream>
#include <cstring>
using namespace std;
int mapp[10][10];
char s[10][10];
int t; bool flag;
bool ok(int num,int digit)
{
int hang=num/9; int lie=num%9;
for(int i=0;i<9;i++)
{
if(mapp[i][lie]==digit)
return false;
}
for(int i=0;i<9;i++)
{
if(mapp[hang][i]==digit)
return false;
}
//开始判断小正方形了
int xhang=3*(hang/3);
int xlie=3*(lie/3);
for(int i=xhang;i<3+xhang;i++)
{
for(int j=xlie;j<3+xlie;j++)
{
if(digit==mapp[i][j])
return false;
}
}
return true;
}
void dfs(int num)
{
if(num>80||flag)//这个flag就是返回时候的算是标记吧
{
flag=true; return ;
}
int line=num/9;
int lie=num%9;
if(mapp[line][lie]!=0)//说明已经填好了
{
dfs(num+1);
if(flag)
return ;
}
else
{
for(int i=1;i<=9;i++) //数字
{
if(ok(num,i))
{
mapp[line][lie]=i;
dfs(num+1);
if(flag) return ;
mapp[line][lie]=0;
}
}
}
}
int main()
{
scanf("%d",&t);
while(t--)
{
flag=false;
memset(mapp,0,sizeof(mapp));
getchar();
for(int i=0;i<9;i++)
{
//getchar();
for(int j=0;j<9;j++)
{
scanf("%c",&s[i][j]);
if(s[i][j]=='*')
mapp[i][j]=0;
else
mapp[i][j]=s[i][j]-'0';
}
getchar();
}
dfs(0); //从第一个格子开始找 下标为0
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
printf("%d",mapp[i][j]);
}
printf("\n");
}
printf("\n");
}
return 0;
}
这个就是最近接触的dfs,下面简单说一下bfs,我觉得bfs是一种模板,通常它和队列的一起的,不同于dfs,dfs是不断的调用同一个函数多次,而bfs不一样,它是一个板子,只是在队列不为空的情况下进行的广搜,通常来说bfs是来解决最小,最少的问题,当然也有一部分迷宫问题,迷宫问题也有用bfs,具体题目需要具体分析,不能把思想局限在bfs只能解决迷宫,然后就是bfs是将接下来可以满足的条件入队列,然后在满足条件下一步步搜索,这是我这几天做题时候的感觉;废话不多说 ,开始看题目:
从最基本的题目开始:
迷宫问题:
题目1:https://vjudge.net/contest/317155#problem/D
D - 十四大师
在很久很久以前,有一位大师级程序员,实力高强,深不可测,代码能力无人能及。从来没有人听说过他的真名,只知道他在完成一段代码后,总会跟上一行注释“十四出品,必属精品”,于是他在编程江湖上便有了绰号“十四”。
然而,十四大师并不满足于现有的一切,他想要让自己的实力有更进一步的提升。为此,他专程前往二次元世界修行。
二次元旅程归来的十四大师学习了新的技能——闪现。
在一条既定的直线道路上,“闪现”技能允许十四大师超时空移动。如果把道路视为一条数轴,使用闪现可以让十四大师瞬间移动到脚下坐标两倍的位置上。例如,如果十四大师站在坐标5的位置上,他可以直接闪现到坐标10的位置,如果继续闪现,则可以到达坐标20的位置上。
现在十四大师打算练习一下“闪现”在生活中的应用。我们假定他站在坐标为a的位置上,而他想要到达坐标为b的位置(0 ≤a,b≤100000)。除了使用闪现外,他也可以像常人一样徒步向前或者向后走,而使用闪现视为行走了一步。请问十四大师最少需要走多少步才可以到达目标?
Input
输入包含多组数据。每组数据占一行,包含两个整数a和b,表示十四大师的起始坐标和目的地坐标。(0 ≤a,b≤100000)
Output
对于每组输入,输出一个整数,即十四大师到达目的地的最少步数。
Sample Input
5 17
Sample Output
4
Hint
对于样例数据,最少步数的走法是:从坐标5闪现到坐标10,后退一步到坐标9,再闪现到坐标18,最后后退一步即到达坐标17。总共四步。
思路 这道题目应该可以说是bfs最最经典的模板类似了;和杭电的牛吃草一样;
#include <iostream>
#include <queue>
#include <cstring>
const int maxn=1e5+5;
#define inf 0x3f3f3f3f
using namespace std;
int n,m,ans;
struct node
{
int pos,pre;
};
int vis[maxn];
void bfs(int n,int m)
{
memset(vis,0,sizeof(vis));
queue<node>q;
node t,v;
ans=inf;
t.pos=n,t.pre=0;
vis[n]=1;
q.push(t);
while(!q.empty())
{
v=q.front(); q.pop();
if(v.pos==m)
{
ans=min(ans,v.pre);
}
node c;
c.pos=v.pos+1;
if(c.pos<=maxn&&!vis[c.pos])
{
vis[c.pos]=1;
c.pre=v.pre+1;
q.push(c);
}
c.pos=v.pos-1;
if(c.pos>=0&&!vis[c.pos])
{
vis[c.pos]=1;
c.pre=v.pre+1;
q.push(c);
}
c.pos=v.pos*2;
if(c.pos<=maxn&&!vis[c.pos])
{
vis[c.pos]=1;
c.pre=v.pre+1;
q.push(c);
}
}
}
int main()
{
scanf("%d %d",&n,&m);
bfs(n,m);
printf("%d\n",ans);
return 0;
}
将三种情况分别判断,入栈,在很多中情况下,寻找最好的解决方式,最短路径!,当时看到这道题目,我是没有用队列,直接暴力bfs,一直wa,不明白为什么后来在一位大佬的执教下明白了,我把超时的代码贴在下面,引以为戒,注意观察这道题目的数据范围,1e6,如果单跑的话,函数跑到4万三千多次(亲测)就结束了,显然这个wa代码是查找次数超了;代码如下:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <queue>
#include <cstring>
#include <string>
#define ll long long
#define inf 0x3f3f3f3f
#define sf scanf
#define min(x,y) x<y ? x : y
const int maxn=3*1e6+66;
#define pi acos(-1.0)
using namespace std;
int n,k,ans=inf;
int vis[maxn];
bool judge(int x)
{
if(x>=0&&x<=100000&&!vis[x])
return true;
return false;
}
void bfs(int x,int num)
{
int now;
if(x==k)
{
ans=min(ans,num);
}
else if(num<=ans)
{
vis[x]=1;
if(x+1>=0&&x+1<=100000&&!vis[x+1])
bfs(x+1,num+1);
if(x-1>=0&&x-1<=100000&&!vis[x-1])
bfs(x-1,num+1);
if(x*2>=0&&x*2<=100000&&!vis[x*2])
bfs(x*2,num+1);
vis[x]=0;
}
}
int main()
{
while(sf("%d %d",&n,&k))
{
if(k<=n)
printf("%d\n",n-k);
else
{
memset(vis,0,sizeof(vis));
bfs(n,0);
printf("%d\n",ans);
}
}
return 0;
}
第二道题目:A - 素数小姐姐
孤单的zydsg又一次孤单的度过了520,不过下一次不会再这样了。zydsg要做些改变,他想去和素数小姐姐约会。
所有的路口都被标号为了一个4位素数,zydsg现在的位置和素数小姐姐的家也是这样,如果两个路口间只差1个数字,则有一条路连通两个路口。(例如1033和1073间有一条路连接)
现在,你知道了zydsg的位置和素数小姐姐的家,问最少zydsg要走多少条路才能见到素数小姐姐。例如:如果zydsg在1033,素数小姐姐的家在8179,最少要走6条街,走法为:
1033
1733
3733
3739
3779
8779
8179
Input
输入数据有多组,首先是一个数字n,代表之后有n组数据。 其次,在每一组输入中,都包含两个数字a和b,代表zydsg的位置和素数小姐姐家的位置。 其中,a和b都是四位数,而且不含前导0。
Output
每组输入输出一行,表示zydsg最少需要走多少条路。若不存在合法的路径,则输出单词“Impossible”。
Sample Input
3
1033 8179
1373 8017
1033 1033
Sample Output
6
7
0
题目链接:https://vjudge.net/contest/317155#problem/A
思路:需要判断一个素数是否是素数,数据范围比较小,我们枚举每一种可能的情况,从个位,十位,百位,千位观察每一种情况,代码中素数判断的方法,是用的素数筛;然后对于模拟每一位;代码如下:a数组是用来标记的,b数组是来记录次数的;
#include <iostream>
#include <queue>
#define maxn 10050
#include <cstring>
using namespace std;
int ifprime[maxn]={1,1},k,x,y;
int prime[maxn],a[maxn],b[maxn];
queue<int> q;
int neww,v,flag;
void init()
{
int total=0;
for(int i=2;i<maxn;i++)
{
if(!ifprime[i])
{
prime[total++]=i;
}
for(int j=0;j<total&&i*prime[j]<maxn;j++)
{
ifprime[i*prime[j]]=1;
if(i%prime[j]==0) break;
}
}
}
int main()
{
scanf("%d",&k);
init();
while(k--)
{
scanf("%d %d",&x,&y);
q.push(x);
a[x]=1;
flag=0;
b[x]=0;
int c,s;
while(!q.empty())
{
c=q.front(); q.pop();
if(c==y)
{
flag=1; break;
}
for(int i=1;i<=9;i++) //个位
{
s=c/10*10+i;
if(!a[s]&&!ifprime[s])
{
a[s]=1;
q.push(s);
b[s]=b[c]+1;
}
if(s==y)
{
flag=1; break;
}
}
for(int i=0;i<=9;i++) //十位
{
s=c/100*100+i*10+c%10;
if(!a[s]&&!ifprime[s])
{
a[s]=1;
q.push(s);
b[s]=b[c]+1;
}
if(s==y)
{
flag=1; break;
}
}
for(int i=0;i<=9;i++) //百位
{
s=c/1000*1000+i*100+c%100;
if(!a[s]&&!ifprime[s])
{
a[s]=1;
q.push(s);
b[s]=b[c]+1;
}
if(s==y)
{
flag=1; break;
}
}
for(int i=1;i<=9;i++) //千位
{
s=i*1000+c%1000;
if(!a[s]&&!ifprime[s])
{
a[s]=1;
q.push(s);
b[s]=b[c]+1;
}
if(s==y)
{
flag=1; break;
}
}
}
if(flag)
printf("%d\n",b[y]);
else
printf("Impossible\n");
while(!q.empty())
q.pop();
memset(b,0,sizeof(b));
memset(a,0,sizeof(a));
}
return 0;
}
第三道题目:M - 冲冲冲
jxf和hj最近迷上了游戏pokemon go。在训练回家之前,他们想抓一只稀有ztw来攒攒人品(因为土拨鼠的刷新地点最近来到了附近)
但是由于ztw过于强大,他的手撕线段树以及脚踩KMP都可以轻松的将他们两击败,但是他们两的合击必杀技AC自动机fail树上dfs序建可持久化线段树可以将ztw打至昏迷状态,并可将其捕获。
但是因为这是款按时间付费的游戏,他们需要尽快捕捉到ztw(即他们两到ztw的时间之和需要最少),因此他们找到了你来帮他们解决这个问题。 规定每走一步需要花费11分钟。
Input
输入存在多组(需使用!=EOF)
每组的第一行有两个整数n,m(2<=n,m<=200)
接下来n行,每行包括m个字符
‘Y’表示jxf所在的位置
‘M’表示hj所在的位置
‘.’表示可以通过的地方
‘#’表示教学楼即不能走的地方
‘@’表示稀有ztw刷新的地方(地图中存在多只稀有ztw)
(友情提醒:Y和M这两个地方也是不能走的!!!)
Output
对于每组样例输出他们到达土拨鼠刷新点的最小时间总和。
保证每组样例都存在一个土拨鼠刷新点,他们两都能到达
Sample Input
4 4
Y.#@
…
.#…
@…M
4 4
Y.#@
…
.#…
@#.M
5 5
Y…@.
.#…
.#…
@…M.
#…#
Sample Output
66
88
66
这个题目也是属于很经典的双刷bfs来寻找最优解;函数两次bfs分别找到每一个寻找到怪物的最少路径;用一个三维数组来记录,这里也是属于技巧了,就像vis,map数组一样,都是用图的位置(也就是下标)来表示到这个点的次数或者步数,这个技巧很常用,一样的套路,但是要注意给数组清零,因为bfs两次,代码如下:
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
char mapp [400][400];
int vis[400][400];
int flag[3][300][300];
int n,m,ans;
int dxx[4]={1,-1,0,0};
int dyy[4]={0,0,1,-1};
struct node
{
int x,y,pre;
};
bool judge(int x,int y)
{
if(x>=0&&x<n&&y>=0&&y<m&&!vis[x][y]&&mapp[x][y]!='Y'&&mapp[x][y]!='M'&&mapp[x][y]!='#')
return true;
return false;
}
void bfs(int pos,int dx,int dy)
{
memset(vis,0,sizeof(vis));
queue<node>q;
vis[dx][dy]=1;
node v,f;
int xx,yy,ex,ey;
v.pre=0,v.x=dx,v.y=dy;
q.push(v);
while(!q.empty())
{
v=q.front(); q.pop();
//xx=v.x,yy=v.y;
if(mapp[v.x][v.y]=='@')
{
flag[pos][v.x][v.y]=v.pre;
}
for(int i=0;i<4;i++)
{
f.x=v.x+dxx[i];
f.y=v.y+dyy[i];
if(judge(f.x,f.y))
{
vis[f.x][f.y]=1;
f.pre=v.pre+1;
q.push(f);
}
}
}
}
int main()
{
int bx1,by1,bx2,by2;
while(scanf("%d %d",&n,&m)!=EOF)//n行m列
{
getchar();
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
scanf("%c",&mapp[i][j]);
if(mapp[i][j]=='Y')
bx1=i,by1=j;
if(mapp[i][j]=='M')
bx2=i,by2=j;
}
getchar();
}
memset(flag,0x3f3f3f,sizeof(flag));
bfs(1,bx1,by1);
bfs(2,bx2,by2);
ans=0x3f3f3f;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if(mapp[i][j]=='@')
{
ans=min(ans,flag[1][i][j]+flag[2][i][j]);
}
}
}
printf("%d\n",ans*11);
}
return 0;
}
再来一个三维的迷宫:B - Dungeon Master
链接:https://vjudge.net/contest/317155#problem/A
你被困在一个3D地牢里,需要找到最快捷的出路!地牢由单位立方体组成,可以填充或不填充岩石。向北,向东,向西,向上或向下移动一个单位需要一分钟。你不能对角移动,迷宫的四周都是坚硬的岩石。
逃脱可能吗?如果是,需要多长时间?
输入
输入包含许多地下城。每个地下城描述都以包含三个整数L,R和C(全部限制为30个大小)的行开头。
L是组成地牢的等级数。
R和C是构成每个级别计划的行数和列数。
然后将跟随L个R行,每行包含C个字符。每个角色描述一个地牢的一个单元格。充满岩石的细胞用’#‘表示,空细胞用’’'表示。您的起始位置用’S’表示,出口用’E’表示。每个级别后面都有一个空白行。对于L,R和C,输入以三个零结束。
产量
每个迷宫产生一行输出。如果可以到达出口,请打印表格的一行
在x分钟内逃脱。
其中x被逃逸所需的最短时间所取代。
如果无法逃脱,请打印该线
被困!
3 4 5
S…
.###.
.##…
###.#
##.##
##…
#.###
####E
1 3 3
S##
#E#
0 0 0
Sample Output
Escaped in 11 minute(s).
Trapped!
这个题目和上面的题目很类似,只不过它是一个三维的,移动方向多了一些,但是思路是一样的,这里就不多细说了,仔细看代码;
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
int dxx[6]={0,0,1,-1,0,0};
int dyy[6]={0,0,0,0,1,-1};
int dzz[6]={1,-1,0,0,0,0};
int vis[30][30][30];
char mapp[35][35][35];
struct node
{
int x,y,z,pos;
};
int k,dx,dy,dz,ex,ey,ez,ans,z,n,m;
bool judge(int x,int y,int z)
{
if(x>=0&&x<n&&y>=0&&y<m&&z>=0&&z<k&&mapp[z][x][y]!='#')
return true;
return false;
}
void bfs()
{
queue< node > q;
node v,f;
v.x=dx, v.y=dy, v.z=dz, v.pos=0;
q.push(v);
while(!q.empty())
{
f=q.front(); q.pop();
if(f.x==ex&&f.y==ey&&f.z==ez)
{
ans=f.pos; return ;
}
for(int i=0;i<6;i++)
{
v.x=f.x+dxx[i];
v.y=f.y+dyy[i];
v.z=f.z+dzz[i];
if(!vis[v.z][v.x][v.y]&&judge(v.x,v.y,v.z))
{
vis[v.z][v.x][v.y]=1;
v.pos=f.pos+1;
q.push(v);
}
}
}
}
int main()
{
while(scanf("%d %d %d",&z,&n,&m))
{
ans=0;
if(n==0&&m==0&&z==0) break;
getchar();
for(k=0;k<z;k++)
{
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
scanf("%c",&mapp[k][i][j]);
if(mapp[k][i][j]=='S')
dx=i,dy=j,dz=k;
if(mapp[k][i][j]=='E')
ex=i,ey=j,ez=k;
}
getchar();
}
getchar();
}
memset(vis,0,sizeof(vis));
vis[dz][dx][dy]=1;
bfs();
if(ans)
printf("Escaped in %d minute(s).\n",ans);
else
printf("Trapped!\n");
}
return 0;
}
再来一个这个题目的升级版:H - 三维迷宫
链接:https://vjudge.net/contest/317155#problem/H
可怜的公主在一次次被魔王掳走一次次被骑士们救回来之后,而今,不幸的她再一次面临生命的考验。魔王已经发出消息说将在T时刻吃掉公主,因为他听信谣言说吃公主的肉也能长生不老。年迈的国王正是心急如焚,告招天下勇士来拯救公主。不过公主早已习以为常,她深信智勇的骑士LJ肯定能将她救出。
现据密探所报,公主被关在一个两层的迷宫里,迷宫的入口是S(0,0,0),公主的位置用P表示,时空传输机用#表示,墙用表示,平地用.表示。骑士们一进入时空传输机就会被转到另一层的相对位置,但如果被转到的位置是墙的话,那骑士们就会被撞死。骑士们在一层中只能前后左右移动,每移动一格花1时刻。层间的移动只能通过时空传输机,且不需要任何时间。
Input
输入的第一行C表示共有C个测试数据,每个测试数据的前一行有三个整数N,M,T。 N,M迷宫的大小NM(1 <= N,M <=10)。T如上所意。接下去的前NM表示迷宫的第一层的布置情况,后NM表示迷宫第二层的布置情况。
Output
如果骑士们能够在T时刻能找到公主就输出“YES”,否则输出“NO”。
Sample Input
1
5 5 14
S*#*.
.#…
…
****.
…#.
….P
#.…
**…
….
*.#…
Sample Output
YES
思路: 刚开始做这个题目的时候,比较随意,结果卡了一个晚上 ,也算个警醒吧,因为当时做的时候,想的比较简单,然后就一直wa,这个题目相对上个题目是有一些变化,首先多了一个传送门,只有在传送门的时候你才有资格传送,当时我做的时候就是错在这里了,我当时写的是只要有传送门就换层数,然后看了别人的代码,发现bug,对于传送门来说,情况也分三种吧,1:传送的位置也是个传送门,那意思就是这个传送门没用,2,3其实是一样的,就是一层是传送门,一层是墙,这样的情况也是错误的,照着这个思路改,就可以了,简单的来说可以把这些不满足的情况传送门都变成墙,改一下mapp数组,这样题目就变的简单了,代码如下:
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
int t,n,m,dy,dz,dx,step,ans;
char mapp[2][12][12];
int vis[2][12][12];
int dxx[4]={1,-1,0,0};
int dyy[4]={0,0,1,-1};
struct node
{
int pos,x,y,z;
};
bool judge(int x,int y,int z)
{
if(vis[z][x][y]==0&&x>=0&&x<n&&y>=0&&y<m&&mapp[z][x][y]!='*')
return true;
return false;
}
void bfs()
{
memset(vis,0,sizeof(vis));
queue<node> q;
node v,f;
v.x=0,v.y=0,v.pos=0,v.z=0;
vis[0][0][0]=1;
q.push(v);
while(!q.empty())
{
v=q.front(); q.pop();
//printf("%d__ %d__ %d__ %d\n",v.x,v.y,v.z,v.pos);
if(v.pos>step) return ;
if(v.x==dx&&v.y==dy&&v.z==dz)
{
ans=v.pos; return ;
}
for(int i=0;i<4;i++)
{
f.x=v.x+dxx[i];
f.y=v.y+dyy[i];
if(judge(f.x,f.y,v.z))
{
vis[v.z][f.x][f.y]=1;
if(mapp[v.z][f.x][f.y]=='#')
{
f.z=!v.z;
vis[f.z][f.x][f.y]=1;
}
else
{
f.z=v.z;
}
f.pos=v.pos+1;
q.push(f);
}
}
}
}
int main()
{
scanf("%d",&t);
while(t--)
{
ans=0;
scanf("%d %d %d",&n,&m,&step);
getchar();
for(int k=0;k<2;k++)
{
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
scanf("%c",&mapp[k][i][j]);
if(mapp[k][i][j]=='P')
dx=i,dy=j,dz=k;
}
getchar();
}
getchar();
}
// printf("%d %d %d\n",dx,dy,dz);
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if(mapp[0][i][j]=='#'&&mapp[1][i][j]=='#')
mapp[0][i][j]='*',mapp[1][i][j]='*';
if(mapp[0][i][j]=='#'&&mapp[1][i][j]=='*')
mapp[0][i][j]='*';
if(mapp[1][i][j]=='#'&&mapp[0][i][j]=='*')
mapp[1][i][j]='*';
}
}
bfs();
//printf("ans==%d\n",ans);
if(ans) printf("YES\n");
else
printf("NO\n");
}
return 0;
}
来一个需要记录路径的:B - 迷宫路径
链接:https://vjudge.net/contest/317155#problem/B
今天阿聪来到了一个滑雪胜地滑雪,但是这个时候前面出现了一座迷宫挡住了他的去路。 坚定的阿聪一定要穿过这座迷宫去滑雪! 为了方便起见,我们定义一个二维数组来表示迷宫:
int maze[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,
};
它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出让阿聪从左上角进入迷宫到右下角离开的最短路线。
Input
一个5 × 5的二维数组,表示一个迷宫。数据保证有唯一解。
Output
左上角到右下角的最短路径,格式如样例所示。
Sample Input
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
Sample Output
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)
这道题目有两种做法,一种是用栈来记录,一种是用数组来记录,两种方法我都贴在下面了;基本的思路还是一样的,多一个记录的过程,贴上代码;
#include <iostream>
#include <stack>
#include <queue>
#include <cstring>
using namespace std;
int dxx[4]={1,-1,0,0};
int dyy[4]={0,0,1,-1};
int mapp[6][6];
int vis[6][6];
struct node1
{
int x,y;
}node[10][10];
queue<node1>q;
stack<node1>ss;
void print()
{
int xx=4,yy=4;
node1 t; t.x=xx,t.y=yy;
while(xx!=-1&&yy!=-1)
{
t.x=xx;
t.y=yy;
ss.push(t);
xx=node[t.x][t.y].x;
yy=node[t.x][t.y].y;
}
while(!ss.empty())
{
node1 f;
f=ss.top();
ss.pop();
printf("(%d, %d)\n",f.x,f.y);
}
}
bool judge(int x,int y)
{
if(x>=0&&x<5&&y>=0&&y<5&&!vis[x][y]&&mapp[x][y]==0)
return true;
return false;
}
void bfs()
{
node1 v,f; //模拟了第一步,就是从(-1,-1)到(0,0)
vis[0][0]=1;
node[0][0].x=-1;//node数组的下标记录了出发的点,内容记录了前驱,前一步的位置
node[0][0].y=-1;
v.x=0,v.y=0;
q.push(v);
while(!q.empty())
{
v=q.front(); q.pop();
int x1=v.x;
int y1=v.y;
if(x1==4&&y1==4)
{
print();
return ;
}
for(int i=0;i<4;i++)
{
int x2=x1+dxx[i];
int y2=y1+dyy[i];
if(judge(x2,y2))
{
vis[x2][y2]=1;
node[x2][y2].x=x1;
node[x2][y2].y=y1;
f.x=x2,f.y=y2;
q.push(f);
}
}
}
}
int main()
{
memset(vis,0,sizeof(vis));
for(int i=0;i<5;i++)
{
for(int j=0;j<5;j++)
scanf("%d",&mapp[i][j]);
}
bfs();
return 0;
}
下面的是pre数组的:
#include <iostream>
#include <cstring>
using namespace std;
int pre[1005];
int map[5][5],vis[5][5];
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
struct node{
int x,y;
}q[1005];
bool judge(int x,int y)
{
if(x>=0&&x<=4&&y>=0&&y<=4&&vis[x][y]==0&&map[x][y]!=1)
return true;
return false;
}
void print(int pos)
{
int t=pre[pos];
if(t==0)
{
printf("(0, 0)\n");
printf("(%d, %d)\n",q[pos].x,q[pos].y);
}
else
{
print(t);
printf("(%d, %d)\n",q[pos].x,q[pos].y);
}
}
void bfs()
{
int l=0,r=1,xx,yy,x,y;
q[0].x=0,q[0].y=0;
memset(vis,0,sizeof(vis));
vis[0][0]=1;
while(l<r)
{
x=q[l].x,y=q[l].y;
if(x==4&&y==4)
{
print(l);
return ;
}
for(int i=0;i<4;i++)
{
xx=x+dx[i];
yy=y+dy[i];
if(judge(xx,yy))
{
q[r].x=xx,q[r].y=yy;
vis[xx][yy]=1;
pre[r]=l;
r++;
}
}
l++;
}
return ;
}
int main()
{
for(int i=0;i<5;i++)
{
for(int j=0;j<5;j++)
scanf("%d",&map[i][j]);
}
bfs();
return 0;
}
再来一个类似的题目,也是保留路径,这个题目有点别扭的地方就是先输入列后输出行;
链接:https://vjudge.net/contest/317077#problem/E
整天待在一个方块里, 骑士感到特别的无聊, 于是, 他决定来一场所走就走的旅行
但他只能走日字, 并且世界是一个不大于8*8的棋盘.你能帮勇敢的骑士制定一个旅行计划吗?
请找出一条路使得骑士能够走遍整个棋盘.骑士可以在任一方块出发或结束
Input
第一行输入一个正整数n, 代表数据组数
对于每组数据, 含有两个正整数p和q(1 <= pq <= 26), 表示一个pq的棋盘.每个方块的位置用两个字符表示, 第一个从A开始,代表列, 第二个从1开始,代表行
Output
每组数据的结果首先是一行"Scenario #i:", i是数据序号, 从1开始
然后输出一行, 是字典序第一的可以走遍棋盘的路径, 接着有一个空行
路径应该是连续的依次所走方块的位置
如果不存在这样的路径, 输出"impossible"
Sample Input
3
1 1
2 3
4 3
Sample Output
Scenario #1:
A1
Scenario #2:
impossible
Scenario #3:
A1B3C1A2B4C2A3B1C3A4B2C4
圈主重点:唯一答案,暗示flag,这也是一道经典的dfs题目,要用到结构体,求路径时候!
/******************************************************
//A Accepted 148 KB 0 ms C++ 1259 B
题意:骑士周游列国,马走日八个方向走,同时也要注意字典序
最后是按照字典序输出的
注意:先输入列再输入行
*******************************************************/
#include<stdio.h>
#include<string.h>
int dxx[8]={-2,-2,-1,-1,1,1,2,2};
int dyy[8]={-1,1,-2,2,-2,2,-1,1};
const int maxn = 30;
int vis[maxn][maxn];
int n,m;
int dir[8][2] = {-2,-1, -2,1, -1,-2, -1,2, 1,-2, 1,2, 2,-1, 2,1};
//注意方向也要按照字典序来 ,否则WA的好惨。。。
struct Path
{
int x,y;
}p[maxn];
int flag;
void dfs(int x, int y, int step)
{
if(flag) return;
p[step].x = x;
p[step].y = y;
if(step == n*m)
{
flag = 1;
return;
}
Path next;
for(int i = 0; i < 8; i++)
{
next.x = x+dxx[i];
next.y = y+dyy[i];
if(next.x >= 1 && next.x <= n && next.y >= 1 && next.y <= m && !vis[next.x][next.y])
{
vis[next.x][next.y] = 1;
dfs(next.x, next.y, step+1);
vis[next.x][next.y] = 0;
}
}
return;
}
int main()
{
int T;
scanf("%d", &T);
for(int t = 1; t <= T; t++)
{
scanf("%d%d", &m,&n); //先输入列再输入行
memset(vis,0,sizeof(vis));
memset(p,0,sizeof(p));
flag = 0;
vis[1][1] = 1;
dfs(1,1,1);
printf("Scenario #%d:\n", t);
if(flag)
{
for(int i = 1; i <= n*m; i++)
printf("%c%d", p[i].x-1+'A', p[i].y);
printf("\n");
}
else printf("impossible\n");
if(t != T) printf("\n");
}
return 0;
}
再来几个有意思的bfs:
题目:J - 倒水 链接:https://vjudge.net/contest/317155#problem/J
给你两个容器,分别能装下A升水和B升水,并且可以进行以下操作
FILL(i) 将第i个容器从水龙头里装满(1 ≤ i ≤ 2);
DROP(i) 将第i个容器抽干
POUR(i,j) 将第i个容器里的水倒入第j个容器(这次操作结束后产生两种结果,一是第j个容器倒满并且第i个容器依旧有剩余,二是第i个容器里的水全部倒入j中,第i个容器为空)
现在要求你写一个程序,来找出能使其中任何一个容器里的水恰好有C升,找出最少操作数并给出操作过程
Input
有且只有一行,包含3个数A,B,C(1<=A,B<=100,C<=max(A,B))
Output
第一行包含一个数表示最小操作数K
随后K行每行给出一次具体操作,如果有多种答案符合最小操作数,输出他们中的任意一种操作过程,如果你不能使两个容器中的任意一个满足恰好C升的话,输出“impossible”
Sample Input
3 5 4
Sample Output
6
FILL(2)
POUR(2,1)
DROP(1)
POUR(2,1)
FILL(2)
POUR(2,1)
这个题目刚做,看到最小操作数想到了用bfs,然后开始枚举每一种状态,也就是贴板子,然后需要注意的一点就是这个题目要输出字符串,可以先写出这个字符串,然后用下标来输出,一共就两个水杯,然后每个水杯有三种状态,自己加满,全部倒掉,然后就是给另一个水杯加水(这里也要分情况讨论),然后就可以模拟了,代码如下:
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
int vis[100][100];//标记水量
int a,b,c;
char way[7][15]={"FILL(1)","DROP(1)","POUR(1,2)","FILL(2)","DROP(2)","POUR(2,1)"};//左边给右边
struct node
{
int a,b,pos;
int lens;
char s[100];
};
void print(node f)
{
printf("%d\n",f.pos);
for(int i=0;i<f.lens;i++)
printf("%s\n",way[(int)f.s[i]]);
}
void bfs()
{
memset(vis,0,sizeof(vis));
queue<node>q;
node f,t;
t.a=0,t.b=0,t.pos=0,t.lens=0;
memset(t.s,0,sizeof(t.s)); //!!!!!s字符串是用来记录操作的顺序 来对应上面字符串way的
vis[t.a][t.b]=1;
q.push(t);
while(!q.empty())
{
f=q.front(); q.pop();
if(f.a==c||f.b==c)
{
print(f);
return ;
}
node v;
v=f;
v.lens++; //需要注意!,判断在前面,此时++就是不满足的意思,所以一定会有下一步操作!
if(f.a<a)//fill(a)
{
v.a=a; v.b=f.b;
if(!vis[v.a][v.b])
{
v.pos=f.pos+1;
v.s[f.lens]=0;
q.push(v);
vis[v.a][v.b]=1;
}
}
if(f.b<b)//fill(b)
{
v.b=b,v.a=f.a;
if(!vis[v.a][v.b])
{
vis[v.a][v.b]=1;
v.pos=f.pos+1;
v.s[f.lens]=3;
q.push(v);
}
}
if(f.a>0)//drop(1)
{
v.a=0,v.b=f.b;
if(!vis[v.a][v.b])
{
vis[v.a][v.b]=1;
v.pos=f.pos+1;
v.s[f.lens]=1;
q.push(v);
}
}
if(f.b>0)//drop(b)
{
v.b=0,v.a=f.a;
if(!vis[v.a][v.b])
{
vis[v.a][v.b]=1;
v.pos=f.pos+1;
v.s[f.lens]=4;
q.push(v);
}
}
if(f.b<b&&f.a)//drop(1,2)
{
if(f.a>(b-f.b))
{
v.a=f.a-(b-f.b);
v.b=b;
}
else
{
v.a=0;
v.b=f.b+f.a;
}
if(!vis[v.a][v.b])
{
vis[v.a][v.b]=1;
v.pos=f.pos+1;
v.s[f.lens]=2;
q.push(v);
}
}
if(f.b&&f.a<a)//drop(2,1)
{
if(f.b>(a-f.a))
{
v.a=a;
v.b=f.b-(a-f.a);
}
else
{
v.a=f.a+f.b;
v.b=0;
}
if(!vis[v.a][v.b])
{
vis[v.a][v.b]=1;
v.pos=f.pos+1;
v.s[f.lens]=5;
q.push(v);
}
}
}
printf("impossible\n");
}
int main()
{
scanf("%d %d %d",&a,&b,&c);
bfs();
return 0;
}
再来一个类似的有意思的题目:L - 喝冰可乐
链接:https://vjudge.net/contest/317156#problem/H
题目:大家一定觉的运动以后喝可乐是一件很惬意的事情,但是seeyou却不这么认为。因为每次当seeyou买了可乐以后,阿牛就要求和seeyou一起分享这一瓶可乐,而且一定要喝的和seeyou一样多。但seeyou的手中只有两个杯子,它们的容量分别是N 毫升和M 毫升 可乐的体积为S (S<101)毫升 (正好装满一瓶) ,它们三个之间可以相互倒可乐 (都是没有刻度的,且 S==N+M,101>S>0,N>0,M>0) 。聪明的ACMER你们说他们能平分吗?如果能请输出倒可乐的最少的次数,如果不能输出"NO"。
Input
三个整数 : S 可乐的体积 , N 和 M是两个杯子的容量,以"0 0 0"结束。
Output
如果能平分的话请输出最少要倒的次数,否则输出"NO"。
Sample Input
7 4 3
4 1 3
0 0 0
Sample Output
NO
3
解这个题目需要明白,最后平分的两杯的两个杯子一定在最大的可乐瓶,和a,b中最大的一个,答案只可能在这两个中间;下面代码中,我将最大的杯子定为了s[2];然后三个杯子分别叫c,a,b;三个杯子各有两种情况,所以一共有六种情况,每一种情况要分对别对应到每个杯子上,即使这次的操作和某个杯子无关,记得也要赋值,然后就是耐心了,代码比较繁琐:
#include <iostream>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
int vis[100][100][100],s[3];
int half,ans;
struct node
{
int t[3],pre;
};
int bfs()
{
memset(vis,0,sizeof(vis));
queue<node> q;
node k,f;
k.t[0]=s[0],k.t[1]=0,k.t[2]=0,k.pre=0;
vis[k.t[0]][k.t[1]][k.t[2]]=1;
q.push(k);
while(!q.empty())
{
f=q.front();
//printf("yes\n");
if(f.t[0]==half&&f.t[2]==half)
{
return f.pre;
}
node v;
if(f.t[0]&&f.t[1]!=s[1])//c倒a
{
if(f.t[0]>(s[1]-f.t[1]))
{
v.t[0]=f.t[0]-(s[1]-f.t[1]);
v.t[1]=s[1];
}
else
{
v.t[1]=f.t[1]+f.t[0];
v.t[0]=0;
}
v.t[2]=f.t[2];
if(!vis[v.t[0]][v.t[1]][v.t[2]])
{
vis[v.t[0]][v.t[1]][v.t[2]]=1;
v.pre=f.pre+1;
q.push(v);
}
}
if(f.t[0]&&f.t[2]!=s[2])//c倒b
{
if(f.t[0]>(s[2]-f.t[2]))
{
v.t[0]=f.t[0]-(s[2]-f.t[2]);
v.t[2]=s[2];
}
else
{
v.t[2]=f.t[2]+f.t[0];
v.t[0]=0;
}
v.t[1]=f.t[1];
if(! vis[v.t[0]][v.t[1]][v.t[2]])
{
vis[v.t[0]][v.t[1]][v.t[2]]=1;
v.pre=f.pre+1;
q.push(v);
}
}
if(f.t[1]&&f.t[0]!=s[0])//a倒c
{
if(f.t[1]>(s[0]-f.t[0]))
{
v.t[1]=f.t[1]-(s[0]-f.t[0]);
v.t[0]=s[0];
}
else
{
v.t[0]=f.t[1]+f.t[0];
v.t[1]=0;
}
v.t[2]=f.t[2];
if(! vis[v.t[0]][v.t[1]][v.t[2]])
{
vis[v.t[0]][v.t[1]][v.t[2]]=1;
v.pre=f.pre+1;
q.push(v);
}
}
if(f.t[1]&&f.t[2]!=s[2])//a倒b
{
if(f.t[1]>(s[2]-f.t[2]))
{
v.t[1]=f.t[1]-(s[2]-f.t[2]);
v.t[2]=s[2];
}
else
{
v.t[2]=f.t[1]+f.t[2];
v.t[1]=0;
}
v.t[0]=f.t[0];
if(! vis[v.t[0]][v.t[1]][v.t[2]])
{
vis[v.t[0]][v.t[1]][v.t[2]]=1;
v.pre=f.pre+1;
q.push(v);
}
}
if(f.t[2]&&f.t[0]!=s[0])//b倒c
{
if(f.t[2]>(s[0]-f.t[0]))
{
v.t[2]=f.t[2]-(s[0]-f.t[0]);
v.t[0]=s[0];
}
else
{
v.t[0]=f.t[2]+f.t[0];
v.t[2]=0;
}
v.t[1]=f.t[1];
if(! vis[v.t[0]][v.t[1]][v.t[2]])
{
vis[v.t[0]][v.t[1]][v.t[2]]=1;
v.pre=f.pre+1;
q.push(v);
}
}
if(f.t[2]&&f.t[1]!=s[1])//b倒a
{
if(f.t[2]>(s[1]-f.t[1]))
{
v.t[2]=f.t[2]-(s[1]-f.t[1]);
v.t[1]=s[1];
}
else
{
v.t[1]=f.t[2]+f.t[1];
v.t[2]=0;
}
v.t[0]=f.t[0];
if(! vis[v.t[0]][v.t[1]][v.t[2]])
{
vis[v.t[0]][v.t[1]][v.t[2]]=1;
v.pre=f.pre+1;
q.push(v);
}
}
q.pop();
}
return 0;
}
int main()
{
while(scanf("%d %d %d",&s[0],&s[1],&s[2]))
{
if(s[0]==0&&s[1]==0&&s[2]==0) break;
if(s[0]&1)
printf("NO\n");
else
{
if(s[1]>s[2]) swap(s[1],s[2]);
half=s[0]/2;
//printf("half==%d\n",half);
int ans=bfs();
if(ans)
printf("%d\n",ans);
else
printf("NO\n");
}
}
return 0;
}
比较恶心的bfs了:
题目:I - Fire Game
链接:https://vjudge.net/contest/317156#rank
胖兄弟和迷宫正在N * M板上玩一种特殊的(无尽)游戏(N行,M列)。一开始,这块木板的每个网格都是由草或空的组成,然后它们开始烧掉所有的草。首先,他们选择两个由草和放火组成的网格。众所周知,火可以在草丛中蔓延。如果网格(x,y)在时间t发射,则与该网格相邻的网格将在时间t + 1发射,该时间引用网格(x + 1,y),(x-1,y), (x,y + 1),(x,y-1)。当没有新网格着火时,此过程结束。如果那时由草组成的所有网格都被解雇了,胖兄弟和迷宫将站在网格的中间并玩一个更特殊(无尽)的游戏。 (也许这是在最后一个问题中解密的OOXX游戏,谁知道。)
你可以假设木板上的草永远不会烧坏,空网格永远不会起火。
请注意,他们选择的两个网格可以是相同的。
输入
日期的第一行是整数T,它是文本案例的数量。
然后是T个案例,每个案例包含两个整数N和M表示板的大小。然后走N行,每行M字符显示板。 “#”表示草。您可以假设至少有一个网格由板中的草组成。
1 <= T <= 100,1 <= n <= 10,1 <= m <= 10
产量
对于每种情况,首先输出案例编号,如果他们可以玩更多特殊(无尽)游戏(开火所有草),输出他们点火后需要等待的最短时间,否则只输出-1。有关详细信息,请参阅示例输入和输出。
样本输入
4
3 3
#。
#。
3 3
#。
#。#
#。
3 3
…
#。#
…
3 3
…#
#。#
样本输出
案例1:1
案例2:-1
案例3:0
案例4:2
题目大意:就是最初你有两个可以选择点火的草堆,位置由你来选择,可以选择在同一个草堆上,然后选择的这个草堆,燃烧不需要花费时间,然后下一秒,草堆周围上下左右四个方向开始然后,问烧完所有的草,需要的最少时间是多少,如果不能就输出 -1;
思路:刚开始我想到了先用dfs来搜索联通块,这个好解决,问题是找到联通块之后怎么模拟,想到这就卡住了,然后参考大佬的博客,然后选择了枚举;枚举每堆草和其它每个草堆燃烧后的情况,这个写,容易理解,每次将选择的两堆草都初试化,存到结构体中,都进队列 ;开始枚举 代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#define inf 0x3f3f3f3f
using namespace std;
struct node
{
int x,y,pos;
}q[105];
int dxx[4]={1,-1,0,0};
int dyy[4]={0,0,1,-1};
int vis[12][12];
char mapp[12][12];
int t,m,n,sum,ans,k;
int bfs(int dx,int dy)
{
int tot=0;
queue<node> qq;
while(!qq.empty()) qq.pop();
//初始化
node v;
v.x=q[dx].x,v.y=q[dx].y,v.pos=0;
vis[v.x][v.y]=1;
qq.push(v);
node f;
f.x=q[dy].x,f.y=q[dy].y,f.pos=0;
vis[f.x][f.y]=1;
qq.push(f);
while(!qq.empty())
{
node c=qq.front(); qq.pop();
tot++;
if(tot==sum) return c.pos;//烧完所有的草了
for(int i=0;i<4;i++)
{
int nx=c.x+dxx[i];
int ny=c.y+dyy[i];
if(nx<0||nx>=n||ny<0||ny>=m||vis[nx][ny]||mapp[nx][ny]=='.')
continue;
vis[nx][ny]=1;
node t;
t.x=nx,t.y=ny,t.pos=c.pos+1;
qq.push(t);
}
}
return 1e9+5;
}
int main()
{
scanf("%d",&t);
k=0;
while(t--)
{
sum=0; //草的数量
scanf("%d %d",&n,&m);
for(int i=0;i<n;i++)
{
scanf("%s",mapp[i]);
for(int j=0;j<m;j++)
{
if(mapp[i][j]=='#')
{
q[sum].x=i,q[sum++].y=j;
}
}
}
ans=1e9+5;
int flag=ans;
for(int i=0;i<sum;i++) //开始枚举每一个草堆的状态 两种选择: 两个火点选择一样的地方,2选两堆草
{
for(int j=i;j<sum;j++)//j==i就是选择1其他的就是选择2
{
memset(vis,0,sizeof(vis));
ans=min(ans,bfs(i,j));
}
}
if(ans!=flag)
printf("Case %d: %d",++k,ans);
else
printf("Case %d: -1\n",++k);
}
return 0;
}
最后一个,放在最后,一定很恶心,很难 ,其实也不难,把这道题写出来就是开拓一下思维吧,因为后来好多bfs的题目,这样的题目都很基础。
题目:J - Fire!
链接:https://vjudge.net/contest/317156#problem/J
乔在迷宫中工作。不幸的是,迷宫的一部分有着火了,迷宫的主人忽略了制造火灾逃避计划。帮助乔逃离迷宫。鉴于乔在迷宫中的位置以及迷宫的哪个方块火上浇油,你必须确定乔是否可以退出迷宫火焰到达了他,他能做多快。乔和火每分钟每分钟移动一个方格,垂直或水平(不是对角线)。火势四个方向蔓延来自每个着火的广场。乔可以退出任何迷宫毗邻迷宫边缘的广场。无论乔还是火可能会进入一个被墙占据的广场。
输入
第一行输入包含一个整数,即测试次数要遵循的案例。每个测试用例的第一行包含两个整数R和C,用空格分隔,1≤R,C≤1000在测试案例的R行之后,每行包含一行迷宫。这些行中的每一行都包含C字符,以及每个字符是以下之一:
• #, 一堵墙
•。,一个可通行的广场
•J,乔在迷宫中的初始位置,这是一个可通行的方格
•F,一个着火的广场
每个测试用例中只有一个J.
产量
对于每个测试用例,如果Joe在之前无法退出迷宫,则输出包含“不可能”的单行
火灾到达他,或者是一个整数,让乔最快可以在几分钟内安全地离开迷宫。
Sample Input
2
4 4
#JF#
#…#
#…#
3 3
#J.
#.F
Sample Output
3
IMPOSSIBLE
思路:刚拿到这个题目想到了要双刷bfs,思路也就是这样,用一个timee数组先跑一遍,筛选出每个位置到火的时间,然后算上j再跑一遍,算出到每个点的时间,如果能跑出这个矩形,需要到每个点的时间小于火到达这个点的时间,这个就是这个代码的核心思想,接下来就是开始模拟吧;注意j在的位置不需要判断;timee数组初试化为inf不然会wa;
#include <iostream>
#include <cstring>
#include <queue>
#define maxn 1005
#define inf 0x3f3f3f3f
using namespace std;
int t,n,m,ans,dx,dy,ex,ey;
struct node
{
int x,y,pos,tim;
};
queue< node > q;
int dxx[4]={1,-1,0,0};
int dyy[4]={0,0,1,-1};
int vis[1005][1005];
int timee[1005][1005];//下标依旧是位置,然后内容是时间
int step[1005][1005];//同理
char mapp[1005][1005];
bool judge(int x,int y)
{
if(x>=0&&x<n&&y>=0&&y<m&&mapp[x][y]=='.'&&!vis[x][y])
return true;
return false;
}
void bfs_time()
{
node f,v;
while(!q.empty())
{
f=q.front(); q.pop();
//printf("__%d\n",timee[f.x][f.y]);
for(int i=0;i<4;i++)
{
v.x=f.x+dxx[i];
v.y=f.y+dyy[i];
if(judge(v.x,v.y))
{
vis[v.x][v.y]=1;
timee[v.x][v.y]=timee[f.x][f.y]+1;
v.tim=timee[v.x][v.y];
q.push(v);
}
}
}
}
int bfs(int dx,int dy)
{
node v,f;
v.pos=0, v.x=dx, v.y=dy;
step[dx][dy]=0;
vis[dx][dy]=1;
q.push(v);
while(!q.empty())
{
v=q.front(); q.pop();
//printf("__%d__%d\n",timee[v.x][v.y],step[v.x][v.y]);
if(v.x==0||v.x==n-1||v.y==0||v.y==m-1)
{
return step[v.x][v.y]+1;
}
for(int i=0;i<4;i++)
{
f.x=v.x+dxx[i];
f.y=v.y+dyy[i];
if(judge(f.x,f.y)&&step[v.x][v.y]+1<timee[f.x][f.y]) //这里也需要判断!!
{
vis[f.x][f.y]=1;
step[f.x][f.y]=step[v.x][v.y]+1;
f.pos=v.pos+1;
q.push(f);
}
}
}
return -1;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d %d",&n,&m);
memset(vis,0,sizeof(vis));
memset(timee,inf,sizeof(timee));//需要注意
for(int i=0;i<n;i++)
{
scanf("%s",mapp[i]);// j是人f是火
for(int j=0;j<m;j++)
{
if(mapp[i][j]=='J')
dx=i,dy=j;
if(mapp[i][j]=='F')
{
vis[i][j]=1;
node n;
n.x=i,n.y=j,n.tim=0;
q.push(n);
timee[i][j]=0;
}
}
}
bfs_time();
memset(vis,0,sizeof(vis));
ans=0;
ans=bfs(dx,dy);
if(ans==-1)
printf("IMPOSSIBLE\n");
else
printf("%d\n",ans);
}
return 0;
}
好吧,再来个放松的!!
题目:G - Shuffle’m Up
链接:https://vjudge.net/contest/317156#problem/G
扑克桌上扑克玩家常见的消遣方式就是洗牌筹码。通过从两堆扑克筹码S1和S2开始来执行改组筹码,每个筹码包含C个筹码。每个堆叠可包含几种不同颜色的芯片。
通过将来自S1的芯片与来自S2的芯片交错来执行实际的混洗操作,如下面针对C = 5所示:
单个结果堆栈S12包含2 * C个芯片。 S12的最底部芯片是S2的最底部芯片。在该芯片之上,是S1的最底层芯片。交错过程继续从S2的底部取第二个芯片并将其置于S12,然后从S1的底部取出第二个芯片,依此类推,直到S1的最顶部芯片置于S12的顶部。
在混洗操作之后,通过从S12获取最底部的C芯片以形成新的S1并且从S12开始最顶部的C芯片以形成新的S2,将S12分成2个新的堆栈。然后可以重复混洗操作以形成新的S12。
对于这个问题,您将编写一个程序来确定是否可以通过将两个堆栈混洗若干次来形成特定的结果堆栈S12。
输入
第一行输入包含一个整数N,(1≤N≤1000),它是后面的数据集的数量。
每个数据集由四行输入组成。数据集的第一行指定整数C,(1≤C≤100),它是每个初始堆栈(S1和S2)中的芯片数。每个数据集的第二行指定堆栈S1中的每个C芯片的颜色,从最底部的芯片开始。每个数据集的第三行从最底部的芯片开始指定堆栈S2中的每个C芯片的颜色。颜色表示为单个大写字母(A到H)。芯片颜色之间没有空白或分隔符。每个数据集的第四行包含2 * C大写字母(A到H),表示S1和S2的混洗所需结果的颜色为零或更多次。首先指定最底部芯片的颜色。
产量
每个数据集的输出由显示数据集编号(1到N)的单行,空格和整数值组成,该值是获取所需结果堆栈所需的最小混洗操作数。如果使用数据集的输入无法达到所需结果,则显示值为负1(-1)的随机数操作。
题目大意很好理解,之前用char数组写过一次,当时用string来写,wa了好多次,现在用string写过了,来记录一下,有一些小细节需要注意!
#include <string>
#include <cstring>
#include <map>
#include <iostream>
using namespace std;
string s,s1,s2,s12;
map<string,int>vis;
int main()
{
int t,flag,c,k=0;
scanf("%d",&t);
while(t--)
{
//memset(vis,0,sizeof(vis));
scanf("%d",&c);
cin>>s1>>s2>>s12;
vis[s12]=1;
flag=0; int pos=0;
while(1)
{
s="";
for(int i=0;i<c;i++)
{
s+=s2[i];
s+=s1[i];
}
//s+='\0'; 不能加
pos++; //次数加1
//if(s==s12)
if(s.compare(s12)==0)//新的比较方法
{
// cout<<"yes"<<endl;
flag=1;
break;
}
if(vis[s]==1&&s.compare(s12))
{
flag=-1;
break;
}
vis[s]=1;
s1="",s2="";
for(int i=0;i<c;i++)
{
s1+=s[i];
s2+=s[c+i];
}
//s1+='\0'; s2+='\0'; 不需要加!!!!
}
if(flag==-1)
cout<<++k<<' '<<"-1"<<endl;
if(flag==1)
cout<<++k<<' '<<pos<<endl;
}
return 0;
}
当然也有没过的,拉出来纪念;
链接:https://vjudge.net/contest/317156#problem/D
链接:https://vjudge.net/contest/317155#problem/Q