每日算法好题之胖胖的牛牛
每逢佳节胖三斤,牛牛在过去的节日里长胖了,连拐弯都困难,甚至会卡在门上,所以他很讨厌拐弯。给你一个N*N(2≤N≤100)的方格中,‘x’表示障碍,‘.’表示没有障碍(可以走),牛牛可以从一个格子走到他相邻的四个格子,但是不能走出这些格子。问牛牛从A点到B点最少需要转90度的弯几次。
思路:
两种做法,这里主要讲第一种,建图+单源最短路(堆优化后的迪杰斯特拉),首先思考为啥可行,把题意抽象化,其实就是求A到B的最短路径,然后既然是图就要考虑建边,也是最难思考的点,也是自己太蠢,简单讲一下我的建边方法:
首先把不是X的点都看作四个小城市,然后确定四个行走方向分别为0,1,2,3,然后接下来,去搜每座城市上下左右相邻的城市去建边,规则是把当前城市+行走的方向,如果和下一城市的标号加上行走的方向相同,那么建边,这两座城市的距离为0,否则这两座城市的距离为1。
为什么呢?因为我假设每个城市的编号都是1,1+4,1+4+4...以此类推,如果加的方向标志数相同,代表它之前也能这样走,所以可以直接连边且边权为0,否则的话,方向标志数不同,但是他们又相邻可以互相到达,就建边权为1的边,这里还是需要自己独立自己好好思考一下的
编辑
需要注意的一点是,因为起点的城市有四个,终点的城市也有四个,所以需要跑四次迪杰斯特拉,然后每一次都去取距离终点城市的距离取最小值~
然后就是堆优化的迪杰斯特拉板子了,一开始算的点的时候没算明白,其实最多的情况是100*100*4条边,属于是稠密图了,需要使用邻接矩阵来储存,所以也需要堆优化的迪杰斯特拉
#include<bits/stdc++.h> using namespace std; typedef pair<int ,int >PII; const int maxn=1e5; const int max1=105; int dx[4]= {1,0,-1,0},dy[4]= {0,1,0,-1}; int dist[maxn]; vector<int>edges[maxn]; char mp[max1][max1]; bool vis[maxn]; int e[maxn],ne[maxn],h[maxn],w[maxn],idx; int st,ed; map<pair<int ,int >,int >mo; int ans=0x3f3f3f3f,cnt=1; void add(int a,int b,int c) { e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++; } void dijkstra(int start) { memset(dist, 0x3f,sizeof dist); //初始化距离 0x3f代表无限大 memset(vis, false,sizeof vis); dist[start]=0; //第一个点到自身的距离为0 priority_queue<PII,vector<PII>,greater<PII>>mo; mo.push({0,start}); while(mo.size()) { auto t=mo.top(); mo.pop(); int ver=t.second,distance=t.first; if(vis[ver]) continue; vis[ver]=1; for(int i=h[ver];i!=-1;i=ne[i]) { int j=e[i]; if(dist[j]>w[i]+distance) { dist[j]=w[i]+distance; mo.push({dist[j],j}); } } // for(int i=1;i<=cnt;i++) cout<<i<<" "<<dist[i]<<endl; } for(int dd=0;dd<=3;dd++) { ans=min(dist[ed+dd],ans); } } int main() { int n,m,i,j,t; memset(h,-1,sizeof h); cin>>n;getchar(); for(i=0;i<n;i++) { for(j=0;j<n;j++) { cin>>mp[i][j]; int x1=i,x2=j; if(x1<0||x1>=n||x2<0||x2>=n||mp[x1][x2]=='x') continue; if(mp[i][j]=='A') st=cnt; if(mp[i][j]=='B') ed=cnt; mo[{x1,x2}]=cnt; cnt+=4; } } for(i=0;i<n;i++) { for(j=0;j<n;j++) { if(mp[i][j]=='x') continue; for(int k=0;k<4;k++) { int x1=i+dx[k],x2=j+dy[k]; if(x1<0||x1>=n||x2<0||x2>=n||mp[x1][x2]=='x') continue; int d1=mo[{i,j}],d2=mo[{x1,x2}]; for(int dd=0;dd<=3;dd++) { int xx=d1+dd,yy=d2+k; if(dd==k)add(xx,yy,0),add(yy,xx,0); else add(xx,yy,1),add(yy,xx,1); } } } } for(int dd=0;dd<=3;dd++) { int bh=st+dd; dijkstra(bh); } if(ans==0x3f3f3f3f){ cout<<-1<<endl; } else { cout<<ans<<endl; } return 0; } /* 3 A x B . . . x x . 6 A . x x x x . . . . . x x . x x . x x . . x x x x x . . . x x x x x B x */
第二种思路:是比较普通的bfs,但是我实现的有点抽象,或者说比较复杂,简单来说就是对于每个点都进行bfs,而队列里需要维护的是点的坐标以及点的方向和走这个方向所需要的最短距离,这几个因素应该是缺一不可的,应该也有可以优化的地方,我写的太过粗糙了hhh
#include<bits/stdc++.h> using namespace std; const int maxn=105; char mp[maxn][maxn]; int dx[4]= {0,0,1,-1},dy[4]= {1,-1,0,0}; int dis[maxn][maxn]; bool vis[maxn][maxn][5]; int main() { int n,i,j,t; pair<int,int >st,ed; cin>>n; getchar(); memset(dis,0x3f,sizeof dis); for(i=0; i<n; i++) { for(j=0; j<n; j++) { cin>>mp[i][j]; if(mp[i][j]=='A') st.first=i,st.second=j; if(mp[i][j]=='B') ed.first=i,ed.second=j; } } queue<pair<pair<int,int >,pair<int,int>>>mo; dis[st.first][st.second]=0; vis[st.first][st.second][0]=1; vis[st.first][st.second][1]=1; vis[st.first][st.second][2]=1; vis[st.first][st.second][3]=1; mo.push({st,{0,0}}); mo.push({st,{0,1}}); mo.push({st,{0,2}}); mo.push({st,{0,3}}); while(!mo.empty()) { pair<int,int >dd; dd=mo.front().first; int f1=mo.front().second.second; int dist=mo.front().second.first; mo.pop(); for(i=0; i<4; i++) { int x1=dd.first+dx[i],x2=dd.second+dy[i]; pair<int,int >m1,m2; m1= {x1,x2}; if(x1<0||x1>=n||x2<0||x2>=n||mp[x1][x2]=='x') continue; if(i==f1&&dist<=dis[x1][x2]) { dis[x1][x2]=dist; m2= {dist,i}; mo.push({m1,m2}),vis[x1][x2][i]=1; } else if(dist+1<=dis[x1][x2]) { dis[x1][x2]=dist+1; m2= {dist+1,i}; mo.push({m1,m2}),vis[x1][x2][i]=1; } } } if(dis[ed.first][ed.second]==1061109567) cout<<-1<<endl; else cout<<dis[ed.first][ed.second]<<endl; return 0; } /* 3 B x A . . . x x . 6 A . x x x x . . . . . x x . x x . x x . . x x x x x . . . x x x x x B x */