PTA Saving James Bond - Hard Version 思路分析及程式碼解析
阿新 • • 發佈:2021-01-13
PTA Saving James Bond - Hard Version 思路分析及程式碼解析v0.9.1
一、前導
1. 需要掌握的知識
- 圖的最短路徑、BFS
2. 題目資訊
- 題目來源:PTA / 拼題A
- 題目地址:Saving James Bond - Hard Version
二、解題思路分析
1. 題意理解
- Saving James Bond - Easy Version 的加強版,不僅要告訴Bond是否可以獲救,還要告訴他具體的逃脫路徑
1. 1 輸入資料
17 15 //島中共有17條鱷魚:下面17行資料,表示每條鱷魚的橫座標和縱座標;Bond的最大跨越距離是15
10 -21
10 21
-40 10
30 -50
20 40
35 10
0 -10
-25 22
40 -40
-30 30
-10 22
0 11
25 21
25 10
10 10
10 35
-30 10
1.2 輸出資料
- 輸出最短路徑長度及相關鱷魚的座標(從島向岸的順序輸出)
4
0 11
10 21
10 35
2. 思路分析(重點)
- 這道題本質是一個單源無權圖最短路徑問題,表面看起來,在 Saving James Bond - Easy Version的基礎上,通過優化BFS演算法就可以解決掉
- 但實際上還是蠻有難度的,依據題目的輸出要求,我們可以看到 If there are many shortest paths, just output the one with the minimum first jump, which is guaranteed to be unique.(當有多條最短路時,輸出第一跳最小的最短路) 首先需要對圖中的相關頂點(鱷魚座標),按第一跳的距離從小到大排序
- 從島中心開始進行BFS,按第一跳從小到大的順序收入頂點,在遍歷頂點的過程中,實時記錄各頂點到源點的距離(dist陣列) 以及 當前頂點的前一跳頂點(path陣列),這樣才能滿足題目的輸出要求
三、具體實現
1. 彎路和bug
- 注意輸出要求:我一開始忽視了當有多條最短路時,輸出第一跳最小的最短路的輸出要求,被卡了很久
2. 程式碼框架(重點)
2.1 採用的資料結構
- 通過結構體陣列儲存鱷魚的座標
#define MaxNodes 101
//不考慮岸的情況下,圖中最多有101個頂點:島和100只鱷魚
struct node
{
int x;
int y;
}a[MaxNodes];
- 對於本題,需要三個陣列配合DFS( )
int FirstDistance[MaxNodes]; //將鱷魚頂點按第一跳的距離從小到大排序,儲存到FirstDistance陣列
int dist[MaxNodes];//頂點i到源點的距離
int path[MaxNodes];//記錄跳躍路徑
2.2 程式主體框架
程式偽碼描述
int main()
{
1.通過結構體陣列記錄鱷魚座標
2.將鱷魚頂點按第一跳的距離從小到大排序
3.優化後的DFS演算法
3.1 從源點開始,按第一跳從近到遠的距離遍歷圖中的頂點
3.2 若滿足登岸要求,列印資料;不滿足時進行迴圈
return 0;
}
2.3 各分支函式
- GetNodesFirstDistance( ) 重點函式,將鱷魚頂點按第一跳的距離從小到大排序,是後面DFS( )函式的基礎。程式實現難度不高,但是有點繞
void GetNodesFirstDistance()
{
sort(FirstDistance+1,FirstDistance+NodesNumber+1,CompareFirstJump);
//通過sort()函式完成排序,需要#include<algorithm>
}
void initialize()
{
for(int i=0;i<=NodesNumber;i++)
{
FirstDistance[i]=i; // i=1 代表頂點1,也就是錄入資料中第一隻鱷魚的座標
}
}
bool CompareFirstJump(vertex a,vertex b)
{
return firstjump(a) < firstjump(b);
} //按第一跳從小到大的順序排序
int firstjump(vertex n)
{
int dist, james_jump;
dist=(a[n].x*a[n].x) + (a[n].y*a[n].y);
james_jump=(radius+BondJumpDist)*(radius+BondJumpDist); //第一跳有島的加成
if(james_jump>=dist)
return dist;
else
return Null;
}
- BFS( ) 與普通的BFS不同,需要通過dist陣列記錄頂點到源點的距離、通過path資料記錄最短路徑中的圖頂點。由於是無權圖,dist每次+1即可。
另外,將第一跳可達的頂點,需要按從近到遠的距離壓入佇列
typedef int vertex;
void BFS()
{
vertex Node,currentNode;
queue<vertex> q;
q.push(source);
currentNode=q.front();
q.pop();
dist[currentNode]=0;
path[currentNode]=Null;
for(int i=1;i<=NodesNumber;i++) //將第一跳可達的頂點,按從近到遠的距離壓入佇列
{
Node=FirstDistance[i];
if(firstjump(Node)==Null) continue; //第一跳為Null,說明不可達
else
{
q.push(Node);
dist[Node]=dist[currentNode]+1;
path[Node]=currentNode;
}
}
while(!q.empty())
{
currentNode=q.front();
q.pop();
if(isSave(currentNode))
{
PrintResult(currentNode);//滿足登岸要求後執行列印
return ;
}
for(int i=1;i<=NodesNumber;i++)//不滿足繼續迴圈
{
if(jump(i,currentNode) && dist[i]==Null)
{
q.push(i);
dist[i]=dist[currentNode]+1;
path[i]=currentNode;
}
}
}
cout<<"0"<<endl;
}
- PrintResult( ) 滿足要求後列印結果,因為要按照從島到岸的方向列印,所以使用堆疊進行輸出:依據path資料,先入棧、再依次出棧
void PrintResult(vertex currentNode)
{
int top;
stack<vertex> s;
cout<<dist[currentNode]+1<<endl; //從鱷魚到岸還要跳一次,所以+1
while(true)
{
s.push(currentNode);
currentNode=path[currentNode];
if(currentNode==source) break;
}
while(!s.empty())
{
top=s.top();
s.pop();
cout<<a[top].x<<" "<<a[top].y<<endl;
}
return;
}
- isSave( ), jump( ) 與easy version一致,在此不表
3. 完整編碼
#include<algorithm> //sort
#include <stack>
#include <queue>
#include <iostream>
using namespace std;
typedef int vertex;
#define MaxNodes 101 // 不考慮岸的情況下,圖中有101個頂點:島和100只鱷魚 (岸是隱藏頂點)
#define distance 42.5 //島的邊緣到岸上的距離
#define radius 7.5 //島半徑
#define Null -1
#define source 0
struct node
{
int x;
int y;
}a[MaxNodes];
int FirstDistance[MaxNodes]; // If there are many shortest paths, just output the one with the minimum first jump . 據此,需要將100只鱷魚 到 島的距離,按從小到大排序。若第一跳無法到達,輸出Null
int dist[MaxNodes];
int path[MaxNodes];
int NodesNumber,BondJumpDist;
bool jump(int New,int Old);
bool isSave(int n);
int BFS(int n); //Breadth First Search
void GetNodesFirstDistance(); //獲取各頂點的第一跳距離,並按從小到大排序,Null代表第一跳不可達
void initialize(); //初始化dist path FirstDistance陣列
bool CompareFirstJump(vertex a,vertex b);
int firstjump(vertex n);
void PrintResult(vertex currentNode);
void BFS();
int main()
{
int x,y;
cin>>NodesNumber>>BondJumpDist;
for(int i=1;i<=NodesNumber;i++) //無論是否願意,所有的輸入資料必須先接收 . i=0代表島,也就是源點
{
cin>>x>>y;
a[i].x=x;
a[i].y=y;
}
if(BondJumpDist>=distance) //Bond天縱奇才,從小島到岸邊,一跳結束戰鬥
{
cout<<'1';
return 0;
}
initialize(); //初始化dist path FirstDistance 三個陣列
GetNodesFirstDistance();//獲取各頂點的第一跳距離,使得FirstDistance陣列 從小到大排序,Null代表第一跳不可達
BFS();
return 0;
}
void BFS()
{
vertex Node,currentNode;
queue<vertex> q;
q.push(source);
currentNode=q.front();
q.pop();
dist[currentNode]=0;
path[currentNode]=Null;
for(int i=1;i<=NodesNumber;i++) //將第一跳可達的頂點,按從近到遠的距離壓入佇列
{
Node=FirstDistance[i];
if(firstjump(Node)==Null) continue; //第一跳為Null,說明不可達
else
{
q.push(Node);
dist[Node]=dist[currentNode]+1;
path[Node]=currentNode;
}
}
while(!q.empty())
{
currentNode=q.front();
q.pop();
if(isSave(currentNode))
{
PrintResult(currentNode);//滿足登岸要求後執行列印
return ;
}
for(int i=1;i<=NodesNumber;i++)//不滿足繼續迴圈
{
if(jump(i,currentNode) && dist[i]==Null)
{
q.push(i);
dist[i]=dist[currentNode]+1;
path[i]=currentNode;
}
}
}
cout<<"0"<<endl;
}
void PrintResult(vertex currentNode)
{
int top;
stack<vertex> s;
cout<<dist[currentNode]+1<<endl; //從鱷魚到岸還要跳一次,所以+1
while(true)
{
s.push(currentNode);
currentNode=path[currentNode];
if(currentNode==source) break;
}
while(!s.empty())
{
top=s.top();
s.pop();
cout<<a[top].x<<" "<<a[top].y<<endl;
}
return;
}
bool jump(int New,int Old)
{
int dist, james_jump;
dist=(a[New].x-a[Old].x)*(a[New].x-a[Old].x) + \
(a[New].y-a[Old].y)*(a[New].y-a[Old].y);
james_jump=BondJumpDist*BondJumpDist;
if(james_jump>=dist) return true;
else return false;
}
bool isSave(int n)
{
if(a[n].x+BondJumpDist >= 50 || a[n].x-BondJumpDist <= -50 || \
a[n].y+BondJumpDist >=50 || a[n].y-BondJumpDist <= -50 )
return true;
else return false;
}
void initialize()
{
for(int i=0;i<=NodesNumber;i++)
{
dist[i]=Null;
path[i]=Null;
FirstDistance[i]=i; // i=1 代表頂點1,也就是錄入資料中第一隻鱷魚的座標
}
}
void GetNodesFirstDistance()
{
sort(FirstDistance+1,FirstDistance+NodesNumber+1,CompareFirstJump); //sort
}
bool CompareFirstJump(vertex a,vertex b)
{
return firstjump(a) < firstjump(b);
}
int firstjump(vertex n)
{
int dist, james_jump;
dist=(a[n].x*a[n].x) + (a[n].y*a[n].y);
james_jump=(radius+BondJumpDist)*(radius+BondJumpDist); //第一跳有島的加成
if(james_jump>=dist)
return dist;
else
return Null;
}