![](http://img.e-com-net.com/image/info8/b8d97b5613f94ed2ba791cad57d0b2ed.gif)
![](http://img.e-com-net.com/image/info8/2f88dd3f1cd145f59c0e47b51acdbd4b.gif)
链接:https://www.nowcoder.com/acm/contest/157/E 来源:牛客网 题目描述 有一只可爱的老青蛙,在路的另一端发现了一个黑的东西,想过去一探究竟。于是便开始踏上了旅途 一直这个小路上有很多的隧道,从隧道的a进入,会从b出来,但是隧道不可以反向走。 这只青蛙因为太老了,所以很懒,现在想请你帮帮慢,问他最少需要几步才可以到达对面。 将小径看作一条数轴,青蛙初始在0上,这只青蛙可以向前跳也可以向后跳,但每次只能跳一格,每跳一格记作一步,从隧道进到隧道出算做一步。 输入描述: 第一行两个数m,n;表示黑色物品在数轴m点上,数轴上总共有n个隧道接下来n行,每行a,b两个数,表示从a进会从b出10 <= m,n <= 2330m 输出描述: 一个数ans表示最小步数 示例1 输入 复制 16 4 2 10 8 15 12 5 13 6 输出 复制 7 说明 0-->1-->2-->10-->9-->8-->15-->16
dfs:用一个vector存储每个洞,下标是入口,数组值为出口,因为这是二维数组,可以防止一个入口有多个出口的情况,再new一个一维数组,将这个数组的所有可能走的步数都赋值为大于取值范围的一个值,然后开始搜索,dfs类似于树的前序遍历。
每次搜索,两个参数k和l,k指的是1~m中到达的那个点,l指的是了多少步,每次用一维数组把到一个点所对应的步数存储下来,方便遍历搜索,可以减少搜索时间,如果到一个点的步数原来有过并且小于现在如果再走这个点后的步数,那么这一步就不会执行,并且深搜主要搜索每个点向前走,向后走还有进洞三种情况,按照题目的意思写出来就把这道题做的差不多啦,注意一维数组和vector数组的关系。
![](http://img.e-com-net.com/image/info8/b8d97b5613f94ed2ba791cad57d0b2ed.gif)
![](http://img.e-com-net.com/image/info8/2f88dd3f1cd145f59c0e47b51acdbd4b.gif)
#include#include #include #include #include using namespace std; vector<int>p[2333]; int k[2333]; int m,n,ans,a,b; void dfs(int x,int l) { k[x]=l; if(x==m) return ; if(k[x+1]>l+1) dfs(x+1,l+1); if(x>0&&k[x-1]>l+1) dfs(x-1,l+1); for(int i=0;i ) { int v=p[x][i]; if(k[v]>l+1) dfs(v,l+1); } } int main() { scanf("%d%d",&m,&n); for(int i=0;i
) { scanf("%d%d",&a,&b); p[a].push_back(b); } for(int i=1;i<=m;i++) k[i]=999999; dfs(0,0); cout< endl; return 0; }
后来用试图用bfs做的时候发现一个问题,在这个题目中,洞的入口可能只有一个出口,不存在一个入口和多个出口的情况,这样我们就可以用一位数组存储,其实也是能通过本例的,但这样做出来有点投机的意思了,这可能是因为出题人的测试数据不够全面导致的?或者我没读清楚题意2333在后期修改代码的时候,我还是考虑了一个入口多个出口的情况。
bfs:和上面的dfs里一样可以使用vector,我这里采用二维数组的方式存储每一个洞,二维数组的第一个下标表示入口,第二个下标表示这个入口相对应的出口的数量,然后数组对应的值用来存储每一个出口,每次走一步看能走到哪几个点,把它们一次放入队列搜索,bfs类似于树的层序遍历。
First入队一个x,指的是当前青蛙所在的点,然后入队青蛙走了的步数,之后就可用循环判断队列是否为空,每次循环都是将青蛙走一步能到的点放入队列,然后每次出队列最前面的x和l,当找到一个x==m的时候,它后面的那个l就是要求的答案了,注意要用一个数组判断哪些点走过了,如果之前走过了,之后又有点要走,那么后面要走的这个点也没必要走了,所以用这个数组就是要每个点只用一遍。
![](http://img.e-com-net.com/image/info8/b8d97b5613f94ed2ba791cad57d0b2ed.gif)
![](http://img.e-com-net.com/image/info8/2f88dd3f1cd145f59c0e47b51acdbd4b.gif)
#include#include #include #include #include using namespace std; int vis[300],t[300][300]; int d[2]={-1,1}; int n,m; void bfs(int x) { queue<int> q; q.push(x); int l=0; q.push(l); vis[x]=l; while(!q.empty()) { x=q.front(); q.pop(); l=q.front(); q.pop(); if(x==m) { printf("%d\n",l); return ; } if(t[x][0]) { for(int i=1;i<=t[x][0];i++) { int xx=t[x][i]; if(!vis[xx]) { vis[xx]=1; q.push(xx); q.push(l+1); } } } for(int i=0;i<2;i++) { int xx=x+d[i]; if(xx<0||vis[xx]) continue; vis[xx]=1; q.push(xx); q.push(l+1); } } } int main() { int a,b; scanf("%d%d",&m,&n); for(int i=0;i ) { scanf("%d%d",&a,&b); t[a][++t[a][0]]=b; } bfs(0); return 0; }
官方题解中说:“做法:dp”。
本题其实是求顶点0到顶点m的最短路径,可以用弗洛伊德(Floyd)算法,它的代码就是先初始化然后三重循环权值修正,这样就完成了顶点到顶点的额最短路径计算。
![](http://img.e-com-net.com/image/info8/b8d97b5613f94ed2ba791cad57d0b2ed.gif)
![](http://img.e-com-net.com/image/info8/2f88dd3f1cd145f59c0e47b51acdbd4b.gif)
#include#include using namespace std; int m,n,a,b; int dp[255][255]; int main() { scanf("%d%d",&m,&n); for(int i=0;i<=m;i++) { for(int j=0;j<=m;j++) { dp[i][j]=0x3f3f3f3f; } if(i>0) dp[i][i-1]=1; if(i<m) dp[i][i+1]=1; } for(int i=0;i ) { scanf("%d%d",&a,&b); dp[a][b]=1; } for(int k=0;k<=m;k++) { for(int v=0;v<=m;v++) { for(int w=0;w<=m;w++) { dp[v][w]=min(dp[v][w],dp[v][k]+dp[k][w]); } } } printf("%d\n",dp[0][m]); return 0; }
在用Floyd算法计算时,还有一个点需要主要,那就是初始化对最大值的设定。一般情况下,用0x7fffffff表示32位int型最大值,但在最短路径算法实现过程中,我们得使用松弛操作,因为代码中有一行
dp[v][w]=min(dp[v][w],dp[v][k]+dp[k][w]);
所以值有可能是两个数加起来的,结果就有可能溢出而变成负数,说白了0x7fffffff不能满足一个无穷大的数加上一个有穷数依然是无穷大而不溢出,甚至如果两个无穷大的数加在一起依然不溢出就更好了,那这时就要用到0x3f3f3f3f,0x3f3f3f3f的十进制是1061109567,是10^9级别的,它可以作为无穷大使用而不致出现数据大于无穷大的情形,0x3f3f3f3f+0x3f3f3f3f=2122219134,满足了无穷大加无穷大还是无穷大的需求,如果我们想要将某个数组清零,我们通常会使用memset(a,0,sizeof(a)),方便又高效,但是当我们想将某个数组全部赋值为无穷大时,就不能使用memset函数而得自己写循环了,因为memset是按字节操作的,它能够对数组清零是因为0的每个字节都是0(一般我们只有赋值为-1和0的时候才使用它)。将无穷大设为0x3f3f3f3f,0x3f3f3f3f的每个字节都是0x3f,所以要把一段内存全部置为无穷大,我们只需要memset(a,0x3f,sizeof(a))。