1072 Gas Station (30 分)
题目大意
从m个站点中选取一个站点,让他离居民区的最近的人最远,并且没有超出服务范围。如果有很多个最远的加油站,输出距离所有居民区平均距离最小的那一个。如果平均值还是一样。输出加油站编号最小的那一个。
基本思路
这题是我做过pta上面最最最难的一道Dij题目,让我想起了高中哪会被最大值里的最小值,最小值里的最大值这类问题支配的恐惧~
因为k组边中会有重复的和两个顶点相同的情况(说明两个顶点间有多条路,应该选择最小的那条路径)(自身到自身,无效数据,应该舍弃掉),所以我们需要把G[i] [i]全部设置为0,并在读入数据时G[a][b]=G[b][a]=min(tempdis,G[a][b]),这样才能保证图本身的有效性。
根据题目意思,我们需要对m个加油站都进行一次Dij算法。对于其中某个加油站,计算出以某个加油站为起点的最近居民区。因为要在m个最近区民区中找到一个距离最大的,所以需要设置变量ansid(初始化为-1)、ansdis、ansaver记录下最终的结果。
对于每一次Dij,有以下说明:
设置居民区的编号为1~n,加油站的编号为n+1 ~ m。以当前加油站index为起点。因为加油站之间也是有连接的,所以我们需要在Dij算法中对n+m个顶点进行最短路径的计算,得到每个顶点距离当前加油站的最短距离数组d。
遍历整个数组d,如果有大于ds的元素,说明这个加油站的设置,会导致有居民区在服务区外,需要舍弃这个加油站。
遍历整个数组d,记录下最小的元素为dis并累加平均距离aver。
到这里,将得到的dis和aver与ansdis和ansaver进行比较,如果满足更优,则更新ansdis和ansaver,并把当前加油站编号index赋值给ansid。
至此,一次Dij算法结束。
当m次Dij算法结束时,如果ansid还是初始值-1,则该题无解,否则输出结果。
代码
具体数据结构和详细思路的设计请看代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int INF = 100000000;
int n, m, k, ds;
int G[1020][1020];
bool visit[1020];
int d[1020];
void dijkstra(){
for (int i = 1; i <= n + m;i++){
int u = -1, MIN = INF;
for (int j = 1; j <= n + m;j++){
if(visit[j]==false&&d[j]<MIN){
u = j;
MIN = d[j];
}
}
if(u==-1)
return;
else
visit[u] = true;
for (int v = 1; v <= n + m;v++){
if((G[u][v]>0&&G[u][v]<INF)&&visit[v]==false&&d[u]+G[u][v]<d[v]){
d[v] = d[u] + G[u][v];
}
}
}
}
int main(){
//读入并初始化数据
fill(G[0], G[0] + 1020 * 1020, INF);
for (int i = 0; i < 1020;i++) G[i][i] = 0;//因为会有G1 G1 9999这样的数据,所以自身到自身的距离初始化为0
fill(d, d + 1020, INF);
cin >> n >> m >> k >> ds;
for (int i = 1; i <= k; i++){
string s1, s2;
int temp;
cin >> s1 >> s2 >> temp;
int a, b;
if(s1[0]=='G'){
s1 = s1.substr(1);
a = n+stoi(s1);//加油站编号位n+1~n+m
}else{
a = stoi(s1);//居民区编号为1~n
}
if(s2[0]=='G'){
s2 = s2.substr(1);
b = n+stoi(s2);
}else{
b = stoi(s2);
}
G[a][b] = G[b][a] = min(G[a][b], temp);//因为两个顶点之间会有多以奥路径,所以需要取最短的路径
}
int ansid=-1;//结果id(最终选址)
double ansdis=-1, ansaver=INF;//结果距离,结果平均距离
//进行m次Dij算法,以每个加油站为起点考虑问题
for (int index = n + 1; index <= n + m;index++){
//将dij算法需要用到的数据结构定义或者重新初始化
double dis = INF, aver = 0; //本次Dij算法的最短距离dis,平均最短距离aver
fill(visit, visit + 1020, false);
fill(d, d + 1020, INF);
d[index] = 0;
//以index加油站为基准,进行Dij算法
dijkstra();
//以这个加油站位基准的情况下,查看有没有在服务区外的居民区
int flag = 0;
for (int i = 1; i <= n;i++){
if(d[i]>ds){
flag = 1;
break;
}
}
if(flag)
continue;
//以这个加油站位基准的情况下,寻找距离该加油站最近的居民区,更新其id、dis,并且累加aver
for (int i = 1; i <= n;i++){
if(d[i]<dis){
dis = d[i];
}
aver +=1.0* d[i];
}
//上述找到了距离该加油站最近的居民区,与ansdis、ansaver进行比较
//如果该居民区是距离基准加油站最近距离中距离最大的,更新ansid、ansdis、ansaver
aver=aver/n;
if(dis>ansdis){
ansid = index;
ansdis = dis;
ansaver = aver;
}else if(dis==ansdis&&aver<ansaver){
ansid = index;
ansaver = aver;
}
}
//输出
if (ansid == -1)
printf("No Solution");
else
printf("G%d\n%.1f %.1f", ansid - n, ansdis, ansaver);
return 0;
}