1 条题解
-
0
Guest MOD
-
0
网格迷宫中的最小转弯次数问题
题目描述
在一个无限大的二维网格迷宫中,有若干障碍格点。起点为
(sx, sy),可以向四个方向移动,遇到障碍会停止。移动本身不花费代价,但每次左转或右转90°需要花费1点代价。给定若干出口坐标,求从起点到每个出口的最小转弯次数。输入格式
- 第一行:4个整数
n, m, sx, sy,分别表示障碍数量、出口数量、起点坐标 - 接下来
n行:每行2个整数(x, y)表示障碍坐标 - 接下来
m行:每行2个整数(tx, ty)表示出口坐标
输出格式
m行:每行一个整数,表示到达对应出口的最小移动代价
解题思路
关键观察
- 移动特性:沿着一个方向可以一直移动直到遇到障碍,这个过程不花费代价
- 代价来源:只有在改变方向时才需要花费代价(每次转弯花费1)
- 状态表示:需要记录当前位置和当前朝向
算法选择:0-1 BFS
由于边权只有0和1,可以使用双端队列BFS:
- 权值为0的边插入队首
- 权值为1的边插入队尾
核心技巧:双倍点建模
为了处理方向信息,我们为每个物理位置创建两个逻辑点:
- 水平点
i:表示在该位置且当前朝向为水平方向(左/右) - 垂直点
i + top:表示在该位置且当前朝向为垂直方向(上/下)
建图策略
-
收集关键点:
- 起点
- 所有障碍周围的点
- 所有出口
-
同行连接:在同一行且相邻无障碍的点之间建立权值为0的水平边
-
同列连接:在同一列且相邻无障碍的点之间建立权值为0的垂直边
-
方向切换:在同一位置的水平和垂直方向之间建立权值为1的切换边
复杂度分析
- 时间复杂度:,主要来自排序和0-1 BFS
- 空间复杂度:,存储关键点和图结构
代码实现(码风猥琐,常数极大)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN=1600005; const ll inf=1e18; int n,t,sx,sy,top; set<pair<int,int> > blk; // 存储所有障碍物坐标 int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1}; // 四个方向:下、右、上、左 pair<int,int> a0[MAXN],ax[MAXN],ay[MAXN]; // 存储所有关键点 int b[MAXN][4]; // b[i][k]表示点i在k方向是否有障碍 map<pair<int,int>,int> id; // 坐标到索引的映射 vector<pair<int,int> > adj[MAXN*2]; // 邻接表,存储图 ll d[MAXN*2]; // 最短距离数组 // 按y坐标排序的比较函数 bool cmpy(pair<int,int> a,pair<int,int> b){ if(a.second!=b.second) return a.second<b.second; return a.first<b.first; } int q[MAXN*2]; // 双端队列数组 void bfs(){ for(int i=1;i<=2*top;i++) d[i]=inf; // 初始化距离为无穷大 int l=top,r=top-1; // 双端队列的左右指针 // 起点有两个状态:水平方向和垂直方向 int u=id[make_pair(sx,sy)]; d[u]=1; // 水平方向起点 q[++r]=u; u+=top; d[u]=1; // 垂直方向起点 q[++r]=u; // 0-1 BFS:权值为0的插队首,权值为1的插队尾 while(l<=r){ u=q[l++]; for(auto e:adj[u]){ int v=e.first,w=e.second; if(d[u]+w<d[v]){ d[v]=d[u]+w; if(w==0) q[--l]=v; // 权值为0,插队首 else q[++r]=v; // 权值为1,插队尾 } } } } pair<int,int> qt[MAXN]; // 存储所有出口坐标 int main(){ scanf("%d%d%d%d",&n,&t,&sx,&sy); a0[++top]=make_pair(sx,sy); // 加入起点 // 读入障碍物 for(int i=1;i<=n;i++){ int x,y; scanf("%d%d",&x,&y); blk.insert(make_pair(x,y)); } // 收集所有关键点:障碍物周围的点 // 因为只能在障碍物前停下,所以关键点包括所有障碍物周围的非障碍点 for(auto it:blk){ int x=it.first,y=it.second; for(int k=0;k<4;k++){ int x1=x+dx[k],y1=y+dy[k]; if(!blk.count(make_pair(x1,y1))){ a0[++top]=make_pair(x1,y1); } } } // 读入出口并加入关键点 for(int i=1;i<=t;i++){ int x,y; scanf("%d%d",&x,&y); qt[i]=make_pair(x,y); a0[++top]=qt[i]; } // 去重并排序关键点 sort(a0+1,a0+top+1); top=unique(a0+1,a0+top+1)-a0-1; memcpy(ax,a0,sizeof(a0)); // 保存按x排序的结果 // 建立坐标到索引的映射,并记录每个点四个方向的障碍情况 for(int i=1;i<=top;i++){ id[a0[i]]=i; int x=a0[i].first,y=a0[i].second; for(int k=0;k<4;k++){ int x1=x+dx[k],y1=y+dy[k]; if(blk.count(make_pair(x1,y1))) b[i][k]=1; } } // 按y坐标排序 sort(a0+1,a0+top+1,cmpy); memcpy(ay,a0,sizeof(a0)); // 第一步:建立水平连接(同一行相邻点的连接) // dir[i][k]表示点i在k方向上的下一个点 int dir[MAXN][4]={0}; int last=1; for(int i=1;i<=top;i++){ // 找到同一行的连续段 if(i<top&&ax[i].first==ax[i+1].first) continue; // 对同一行内的相邻点建立连接 for(int k=last+1;k<=i;k++){ int u=k,v=k-1; // 如果两个点之间没有障碍,则建立双向连接 if(b[u][3]||b[v][1]) continue; // u不能向左或v不能向右 dir[u][3]=v; // u向左连接到v dir[v][1]=u; // v向右连接到u } last=i+1; } // 第二步:建立垂直连接(同一列相邻点的连接) last=1; for(int i=1;i<=top;i++){ // 找到同一列的连续段 if(i<top&&ay[i].second==ay[i+1].second) continue; // 对同一列内的相邻点建立连接 for(int k=last+1;k<=i;k++){ int u=id[ay[k]],v=id[ay[k-1]]; // 如果两个点之间没有障碍,则建立双向连接 if(b[u][2]||b[v][0]) continue; // u不能向下或v不能向上 dir[u][2]=v; // u向下连接到v dir[v][0]=u; // v向上连接到u } last=i+1; } // 第三步:建立图结构 // 使用双倍点:i表示水平状态,i+top表示垂直状态 for(int i=1;i<=top;i++){ int x=ax[i].first,y=ax[i].second; for(int k=0;k<4;k++){ // 添加同一方向上的移动边(权值为0) if(dir[i][k]){ if(k%2) // 水平方向(右、左) adj[i+top].push_back(make_pair(dir[i][k]+top,0)); else // 垂直方向(下、上) adj[i].push_back(make_pair(dir[i][k],0)); } // 检查是否需要添加方向切换边 int x1=x+dx[k],y1=y+dy[k]; if(!blk.count(make_pair(x1,y1))) continue; // 添加方向切换边(权值为1) if(k%2) // 从垂直状态切换到水平状态 adj[i+top].push_back(make_pair(i,1)); else // 从水平状态切换到垂直状态 adj[i].push_back(make_pair(i+top,1)); } } // 执行0-1 BFS求解最短路径 bfs(); // 输出每个出口的结果 for(int i=1;i<=t;i++){ // 如果出口就是起点,代价为0 if(qt[i].first==sx&&qt[i].second==sy){ printf("0\n"); continue; } int u=id[qt[i]]; // 取水平状态和垂直状态的最小值 ll ans=min(d[u],d[u+top]); if(ans==inf) printf("-1\n"); else printf("%lld\n",ans); } return 0; }完结撒花
后记:
aha上的AC代码(卡常):
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN=1600005; const ll inf=1e18; // 快读 inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } // 快写 inline void write(ll x) { if(x<0) { putchar('-'); x=-x; } if(x>9) write(x/10); putchar(x%10+'0'); } int n,t,sx,sy,top; set<pair<int,int> > blk; int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1}; pair<int,int> a0[MAXN],ax[MAXN],ay[MAXN]; int b[MAXN][4]; map<pair<int,int>,int> id; vector<pair<int,int> > adj[MAXN*2]; ll d[MAXN*2]; bool cmpy(pair<int,int> a,pair<int,int> b){ if(a.second!=b.second) return a.second<b.second; return a.first<b.first; } int q[MAXN*2]; void bfs(){ for(int i=1;i<=2*top;i++) d[i]=inf; int l=top,r=top-1; int u=id[make_pair(sx,sy)]; d[u]=1; q[++r]=u; u+=top; d[u]=1; q[++r]=u; while(l<=r){ u=q[l++]; for(auto e:adj[u]){ int v=e.first,w=e.second; if(d[u]+w<d[v]){ d[v]=d[u]+w; if(w==0) q[--l]=v; else q[++r]=v; } } } } pair<int,int> qt[MAXN]; int main(){ // 使用快读替换scanf n=read(); t=read(); sx=read(); sy=read(); a0[++top]=make_pair(sx,sy); for(int i=1;i<=n;i++){ int x=read(), y=read(); blk.insert(make_pair(x,y)); } for(auto it:blk){ int x=it.first,y=it.second; for(int k=0;k<4;k++){ int x1=x+dx[k],y1=y+dy[k]; if(!blk.count(make_pair(x1,y1))){ a0[++top]=make_pair(x1,y1); } } } for(int i=1;i<=t;i++){ int x=read(), y=read(); qt[i]=make_pair(x,y); a0[++top]=qt[i]; } sort(a0+1,a0+top+1); top=unique(a0+1,a0+top+1)-a0-1; memcpy(ax,a0,sizeof(a0)); for(int i=1;i<=top;i++){ id[a0[i]]=i; int x=a0[i].first,y=a0[i].second; for(int k=0;k<4;k++){ int x1=x+dx[k],y1=y+dy[k]; if(blk.count(make_pair(x1,y1))) b[i][k]=1; } } sort(a0+1,a0+top+1,cmpy); memcpy(ay,a0,sizeof(a0)); int dir[MAXN][4]={0}; int last=1; for(int i=1;i<=top;i++){ if(i<top&&ax[i].first==ax[i+1].first) continue; for(int k=last+1;k<=i;k++){ int u=k,v=k-1; if(b[u][3]||b[v][1]) continue; dir[u][3]=v; dir[v][1]=u; } last=i+1; } last=1; for(int i=1;i<=top;i++){ if(i<top&&ay[i].second==ay[i+1].second) continue; for(int k=last+1;k<=i;k++){ int u=id[ay[k]],v=id[ay[k-1]]; if(b[u][2]||b[v][0]) continue; dir[u][2]=v; dir[v][0]=u; } last=i+1; } for(int i=1;i<=top;i++){ int x=ax[i].first,y=ax[i].second; for(int k=0;k<4;k++){ if(dir[i][k]){ if(k%2) adj[i+top].push_back(make_pair(dir[i][k]+top,0)); else adj[i].push_back(make_pair(dir[i][k],0)); } int x1=x+dx[k],y1=y+dy[k]; if(!blk.count(make_pair(x1,y1))) continue; if(k%2) adj[i+top].push_back(make_pair(i,1)); else adj[i].push_back(make_pair(i+top,1)); } } bfs(); for(int i=1;i<=t;i++){ if(qt[i].first==sx&&qt[i].second==sy){ putchar('0'); putchar('\n'); continue; } int u=id[qt[i]]; ll ans=min(d[u],d[u+top]); if(ans==inf) { putchar('-'); putchar('1'); putchar('\n'); } else { write(ans); putchar('\n'); } } return 0; } - 第一行:4个整数
- 1
信息
- ID
- 2608
- 时间
- 3000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- (无)
- 递交数
- 4
- 已通过
- 0
- 上传者