PAT-ADVANCED1072——Gas Station
我的PAT-ADVANCED程式碼倉:https://github.com/617076674/PAT-ADVANCED
原題連結:https://pintia.cn/problem-sets/994805342720868352/problems/994805396953219072
題目描述:
題目翻譯:
1072 加油站
加油站的位置選取,應該使得其與任何住宅之間的最小距離儘可能遠。同時又必須保證所有的居民都在自己的服務範圍內。
現在給你一張城市的地圖和幾個加油站的候選位置,你需要給出最好的建議。如果有超過1種方案,選取離所有住宅平均距離最小的方案。如果方案還不唯一,輸出編號最小的加油站位置。
輸入格式:
每個輸入檔案包含一個測試用例。在每個測試用例中,第一行給出4個正整數:N(<= 1000),代表房子數量;M(<= 10),代表加油站的候選位置總數;K(<= 10000),代表連線加油站和房子的道路數目;Ds,代表加油站的最大服務距離。所有房子的編號為1 ~ N。所有加油站的候選位置編號從G1 ~ GM。
接下來的K行,每行以下述形式描述一條道路:
P1 P2 Dist
P1和P2代表道路的兩端點,兩端點均既有可能是房子編號也有可能是加油站的候選位置編號,Dist是道路的長度,是一個整數。
輸出格式:
對每一個測試用例,在第一行輸出最好的加油站選址。第二行輸出該候選位置離房子的最近距離和平均距離。數字必須以一個空格分隔且每個數字精確到小數點後面1位。如果沒有任何解決方案,只需輸出No Solution。
輸入樣例1:
4 3 11 5
1 2 2
1 4 2
1 G1 4
1 G2 3
2 3 2
2 G2 1
3 4 2
3 G3 2
4 G1 3
G2 G1 1
G3 G2 2
輸出樣例1:
G1
2.0 3.3
輸入樣例2:
2 1 2 10
1 G1 9
2 G1 20
輸出樣例2:
No Solution
知識點:Dijkstra演算法、Bellman-Ford演算法、SPFA演算法
思路一:Dijkstra演算法(鄰接表實現)
首先是輸入的節點標號與加油站編號全部要轉換成數字編號。我假設房子的編號為1 ~ N,加油站的編號為N + 1 ~ N + M。由於輸入的道路兩端有可能是數字也有可能是G開頭的字串,我們統一用字串接收,並設定一個函式change()將接收的字串轉換為數字編號。
我的轉換規則是對G開頭的字串,取其後面一個字元與字元'0'作差,再返回N + 差值。但是這樣做忽略了G10的情況,因此,當G開頭的字串長度是3位時,我們需要返回N + 10。
而我一開始所犯的錯誤是,忽略了N的範圍,N最大可以到達1000。所以這個編號不止有一個字元,我一開始只考慮了N是個位數的情況。因此我們需要用標頭檔案<sstream>中的stringstream型別將數字字串轉換成數字。
對每個加油站都用Dijkstra演算法求其到各個房子的最小距離。
對每個加油站點,首先判斷其到每個房子的最小距離是否小於等於Ds,如果有任何一個值大於Ds,這個加油站點是不可取的。
其次,對可取的加油站點進行篩選。
(1)篩選條件一:與任何住宅之間的最小距離儘可能遠。
(2)篩選條件二:選取離所有住宅平均距離最小的方案。由於住宅總量一定,因此只需選取離所有住宅總距離最小的方案即可。
(3)篩選條件三:輸出編號最小的加油站位置。這個條件很容易滿足,我們只需要按編號從小到大遍歷即可,對於住宅總距離相等的後續方案不考慮。
注意,在發現離住宅更遠的最小距離後,我們不僅要更新離住宅更遠的最小距離值,還需要更新離所有住宅的總距離。
結果需要四捨五入,我的做法是將離所有住宅的總距離先乘以10再除以N,得到一個浮點數型別。再按同樣的做法,不過這次得到的是一個int型別,將兩者相減,如果差大於等於0.5,就將後面得到的int型別增1,最後輸出結果時還需要再除以10,並保留1位小數。
時間複雜度是O(M * (N + M) ^ 2)。空間複雜度是O(N + M + K)。
C++程式碼:
#include<iostream>
#include<vector>
#include<string>
#include<sstream>
using namespace std;
struct node {
int v; //節點編號
int len; //道路長度
node(int _v, int _len) : v(_v), len(_len) {} //建構函式
};
int N; //房子數量
int M; //加油站數量
int K; //道路數量
int Ds; //加油站服務範圍
int INF = 1000000000; //無窮大數
vector<node> graph[1020]; //無向圖,房子的編號為1 ~ N,加油站的編號為N + 1 ~ N + M
int d[1020]; //記錄最短長度
bool visited[1020]; //標記陣列
int totalDistance();
bool validPosition();
int minDistance();
void init();
int change(string s);
void dijkstra(int s);
int main() {
cin >> N >> M >> K >> Ds;
string P1, P2;
int Dist;
for(int i = 0; i < K; i++) {
cin >> P1 >> P2 >> Dist;
int id1 = change(P1);
int id2 = change(P2);
graph[id1].push_back(node(id2, Dist));
graph[id2].push_back(node(id1, Dist));
}
int minLen = 0;
int minTotalLen = INF;
int result = 0;
for(int i = N + 1; i <= N + M; i++) {
dijkstra(i);
if(validPosition()) {
if(minDistance() > minLen) {
result = i;
minLen = minDistance();
minTotalLen = totalDistance(); //此處也要更新minTotalLen
} else if(minDistance() == minLen) {
if(minTotalLen > totalDistance()) {
result = i;
minTotalLen = totalDistance();
}
}
}
}
if(result == 0){
cout << "No Solution" << endl;
return 0;
}
cout << "G" << result - N << endl;
double average = minTotalLen * 10.0 / N;
int averageResult = minTotalLen * 10 / N;
if(average - averageResult >= 0.5){
averageResult++;
}
printf("%.1lf %.1lf\n", minLen * 1.0, averageResult * 1.0 / 10);
return 0;
}
int totalDistance() {
int sum = 0;
for(int i = 1; i <= N; i++) {
sum += d[i];
}
return sum;
}
bool validPosition() {
for(int i = 1; i <= N; i++) {
if(d[i] > Ds) {
return false;
}
}
return true;
}
int minDistance() {
int min = 1;
for(int i = 2; i <= N; i++) {
if(d[i] < d[min]) {
min = i;
}
}
return d[min];
}
void init() {
for(int i = 1; i <= N + M; i++) {
d[i] = INF;
visited[i] = false;
}
}
int change(string s) {
if(s[0] == 'G'){
if(s.length() == 3){
return 10 + N;
}else{
int num = s[1] - '0';
return num + N;
}
}else{
stringstream ss;
ss << s;
int result;
ss >> result;
return result;
}
}
void dijkstra(int s) {
init();
d[s] = 0;
for(int i = 0; i < N + M; i++) {
int u = -1, min = INF;
for(int j = 1; j <= N + M; j++) {
if(!visited[j] && min > d[j]) {
min = d[j];
u = j;
}
}
if(u == -1) {
return;
}
visited[u] = true;
for(int j = 0; j < graph[u].size(); j++) {
int v = graph[u][j].v;
int len = graph[u][j].len;
if(!visited[v]) {
if(d[u] + len < d[v]) {
d[v] = d[u] + len;
}
}
}
}
}
C++解題報告:
思路二:Bellman-Ford演算法(鄰接表實現)(測試點4會超時)
時間複雜度是O(M * K * (N + M))。空間複雜度是O(N + M + K)。
C++程式碼:
#include<iostream>
#include<vector>
#include<string>
#include<sstream>
using namespace std;
struct node {
int v; //節點編號
int len; //道路長度
node(int _v, int _len) : v(_v), len(_len) {} //建構函式
};
int N; //房子數量
int M; //加油站數量
int K; //道路數量
int Ds; //加油站服務範圍
int INF = 1000000000; //無窮大數
vector<node> graph[1020]; //無向圖,房子的編號為1 ~ N,加油站的編號為N + 1 ~ N + M
int d[1020]; //記錄最短長度
int totalDistance();
bool validPosition();
int minDistance();
void init();
int change(string s);
bool bellmanFord(int s);
int main() {
cin >> N >> M >> K >> Ds;
string P1, P2;
int Dist;
for(int i = 0; i < K; i++) {
cin >> P1 >> P2 >> Dist;
int id1 = change(P1);
int id2 = change(P2);
graph[id1].push_back(node(id2, Dist));
graph[id2].push_back(node(id1, Dist));
}
int minLen = 0;
int minTotalLen = INF;
int result = 0;
for(int i = N + 1; i <= N + M; i++) {
bellmanFord(i);
if(validPosition()) {
if(minDistance() > minLen) {
result = i;
minLen = minDistance();
minTotalLen = totalDistance(); //此處也要更新minTotalLen
} else if(minDistance() == minLen) {
if(minTotalLen > totalDistance()) {
result = i;
minTotalLen = totalDistance();
}
}
}
}
if(result == 0) {
cout << "No Solution" << endl;
return 0;
}
cout << "G" << result - N << endl;
double average = minTotalLen * 10.0 / N;
int averageResult = minTotalLen * 10 / N;
if(average - averageResult >= 0.5) {
averageResult++;
}
printf("%.1lf %.1lf\n", minLen * 1.0, averageResult * 1.0 / 10);
return 0;
}
int totalDistance() {
int sum = 0;
for(int i = 1; i <= N; i++) {
sum += d[i];
}
return sum;
}
bool validPosition() {
for(int i = 1; i <= N; i++) {
if(d[i] > Ds) {
return false;
}
}
return true;
}
int minDistance() {
int min = 1;
for(int i = 2; i <= N; i++) {
if(d[i] < d[min]) {
min = i;
}
}
return d[min];
}
void init() {
for(int i = 1; i <= N + M; i++) {
d[i] = INF;
}
}
int change(string s) {
if(s[0] == 'G') {
if(s.length() == 3) {
return 10 + N;
} else {
int num = s[1] - '0';
return num + N;
}
} else {
stringstream ss;
ss << s;
int result;
ss >> result;
return result;
}
}
bool bellmanFord(int s) {
init();
d[s] = 0;
for(int i = 0; i < N + M - 1; i++) {
for(int u = 1; u <= N + M; u++) {
for(int j = 0; j < graph[u].size(); j++) {
int v = graph[u][j].v;
int len = graph[u][j].len;
if(d[u] + len < d[v]) {
d[v] = d[u] + len;
}
}
}
}
for(int u = 1; u <= N + M; u++) {
for(int j = 0; j < graph[u].size(); j++) {
int v = graph[u][j].v;
int len = graph[u][j].len;
if(d[u] + len < d[v]) {
return false;
}
}
}
return true;
}
C++解題報告:
思路三:SPFA演算法(鄰接表實現)
期望時間複雜度是O(k * M * (N + M)),其中k是一個常數,在很多情況下k不超過2,可見這個演算法異常高效,並且經常性地優於堆優化的Dijkstra演算法。空間複雜度是O(N + M + K)。
C++程式碼:
#include<iostream>
#include<vector>
#include<string>
#include<sstream>
#include<queue>
using namespace std;
struct node {
int v; //節點編號
int len; //道路長度
node(int _v, int _len) : v(_v), len(_len) {} //建構函式
};
int N; //房子數量
int M; //加油站數量
int K; //道路數量
int Ds; //加油站服務範圍
int INF = 1000000000; //無窮大數
vector<node> graph[1020]; //無向圖,房子的編號為1 ~ N,加油站的編號為N + 1 ~ N + M
int d[1020]; //記錄最短長度
bool inq[1020];
int countInq[1020];
int totalDistance();
bool validPosition();
int minDistance();
void init();
int change(string s);
bool spfa(int s);
int main() {
cin >> N >> M >> K >> Ds;
string P1, P2;
int Dist;
for(int i = 0; i < K; i++) {
cin >> P1 >> P2 >> Dist;
int id1 = change(P1);
int id2 = change(P2);
graph[id1].push_back(node(id2, Dist));
graph[id2].push_back(node(id1, Dist));
}
int minLen = 0;
int minTotalLen = INF;
int result = 0;
for(int i = N + 1; i <= N + M; i++) {
spfa(i);
if(validPosition()) {
if(minDistance() > minLen) {
result = i;
minLen = minDistance();
minTotalLen = totalDistance(); //此處也要更新minTotalLen
} else if(minDistance() == minLen) {
if(minTotalLen > totalDistance()) {
result = i;
minTotalLen = totalDistance();
}
}
}
}
if(result == 0) {
cout << "No Solution" << endl;
return 0;
}
cout << "G" << result - N << endl;
double average = minTotalLen * 10.0 / N;
int averageResult = minTotalLen * 10 / N;
if(average - averageResult >= 0.5) {
averageResult++;
}
printf("%.1lf %.1lf\n", minLen * 1.0, averageResult * 1.0 / 10);
return 0;
}
int totalDistance() {
int sum = 0;
for(int i = 1; i <= N; i++) {
sum += d[i];
}
return sum;
}
bool validPosition() {
for(int i = 1; i <= N; i++) {
if(d[i] > Ds) {
return false;
}
}
return true;
}
int minDistance() {
int min = 1;
for(int i = 2; i <= N; i++) {
if(d[i] < d[min]) {
min = i;
}
}
return d[min];
}
void init() {
for(int i = 1; i <= N + M; i++) {
d[i] = INF;
inq[i] = false;
countInq[i] = 0;
}
}
int change(string s) {
if(s[0] == 'G') {
if(s.length() == 3) {
return 10 + N;
} else {
int num = s[1] - '0';
return num + N;
}
} else {
stringstream ss;
ss << s;
int result;
ss >> result;
return result;
}
}
bool spfa(int s) {
init();
d[s] = 0;
queue<int> q;
q.push(s);
inq[s] = true;
countInq[s]++;
while(!q.empty()){
int u = q.front();
q.pop();
inq[u] = false;
for(int j = 0; j < graph[u].size(); j++){
int v = graph[u][j].v;
int len = graph[u][j].len;
if(len + d[u] < d[v]){
d[v] = len + d[u];
if(!inq[v]){
q.push(v);
inq[v] = true;
countInq[v]++;
if(countInq[v] > M + N){
return false;
}
}
}
}
}
return true;
}
C++解題報告: