L3-005. 垃圾箱分布
大家倒垃圾的时候,都希望垃圾箱距离自己比较近,但是谁都不愿意守着垃圾箱住。所以垃圾箱的位置必须选在到所有居民点的最短距离最长的地方,同时还要保证每个居民点都在距离它一个不太远的范围内。
现给定一个居民区的地图,以及若干垃圾箱的候选地点,请你推荐最合适的地点。如果解不唯一,则输出到所有居民点的平均距离最短的那个解。如果这样的解还是不唯一,则输出编号最小的地点。
输入格式:
输入第一行给出4个正整数:N(<= 103)是居民点的个数;M(<= 10)是垃圾箱候选地点的个数;K(<= 104)是居民点和垃圾箱候选地点之间的道路的条数;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
这个题目意思有点难懂啊,关键是第一句话:
所以垃圾箱的位置必须选在到所有居民点的最短距离最长的地方,同时还要保证每个居民点都在距离它一个不太远的范围内。
前半句话啥子意思呢?假设现在 1 号垃圾箱到居民点 1 的距离是 2,到居民点 2 的距离是 3,到居民点 3 的距离是 5,那么 1 号垃圾箱到所有居民点的最短距离就是 2。再假设还有一个 2 号垃圾箱,它到居民点 1 的距离是 3,到居民点 2 的距离是 1,到居民点 3 的距离是 4,那么 2 号垃圾箱到所有居民点的最短距离就是 1。那么根据题意,1 号垃圾箱到所有居民点的最短距离为 2, 2号垃圾箱到所有居民点的最短距离为 1,我们此时应该选择 1 号垃圾箱。
那么后半句呢?其实就是保证每一个居民点到选取的垃圾箱位置的最短距离不能超过一个最大值,否则这个垃圾箱位置就不能选取。想想还挺人性化的~
理解了这个,那么别的条件都好理解了,当两个垃圾箱位置到所有居民点的最短距离相同的时候,我们就要选取到所有居民点平均距离最短的垃圾箱,其实就是到所有的居民点的最短距离之和最小的垃圾箱。我们可以把垃圾箱也作为图的顶点,放在居民点的后面。
还有一个问题是垃圾箱编号输入是以 ‘G’ 开头的,我们还需要将其转换为数字,然后加上居民点总数。
现在思路就清晰了,可以用求单源最短路径的算法来求出每个垃圾箱到每个居民点的最短距离,然后再根据题目所给的要求算出符合要求的垃圾箱位置。我这里用的是 Dijkstra 算法:
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <vector>
using namespace std;
const int INF = 999999999;
const int MAXN = 1020;
int distances[11][MAXN];
bool mark[11][MAXN];
int edge[MAXN][MAXN];
// 此处的 n 为居民点数和垃圾箱点数的和
void dijkstra(int dis[], bool mark[], int n, int s) {
for (int i = 1; i <= n; i++) {
dis[i] = i == s ? 0 : INF;
}
for (int i = 1; i <= n-1; i++) { // 缩放 n-1 次
int minn = INF, minIndex;
for (int j = 1; j <= n; j++) {
if (!mark[j] && dis[j] < minn) {
minIndex = j;
minn = dis[j];
}
}
mark[minIndex] = true; // 此点已经选择过
for (int j = 1; j <= n; j++) { // 通过当前距离 s 点最短的点来缩放其他点
dis[j] = min(dis[j], dis[minIndex] + edge[minIndex][j]);
}
}
}
int main() {
int n, m, k, ds;
cin >> n >> m >> k >> ds;
// 初始化图的临接矩阵
fill(edge[0], edge[0]+MAXN*MAXN, INF);
char s[10], e[10];
int intS, intE, weight;
for (int i = 0; i < k; i++) {
cin >> s >> e >> weight;
// 将字符串转换为数字,如果是垃圾箱,那么编号需要加上 n,
// 即把垃圾箱位置作为图的一个顶点并且放在所有居民点的后面
intS = s[0] == 'G' ? atoi(s+1)+n : atoi(s);
intE = e[0] == 'G' ? atoi(e+1)+n : atoi(e);
edge[intS][intE] = edge[intE][intS] = weight;
}
// 算出每一个垃圾箱到所有居民点的最短距离
for (int i = 1; i <= m; i++) {
dijkstra(distances[i], mark[i], m+n, n+i);
}
// 对每一个垃圾箱进行筛选
vector<int> answer;
int minDis = 0;
int sumDis = 0;
for (int i = 1; i <= m; i++) {
int currentMinDis = INF;
int currentSumDis = 0;
for (int j = 1; j <= n; j++) {
if (distances[i][j] > ds) {
// 垃圾箱到某个居民点的最短距离大于题目规定的最大距离,那么这个垃圾箱位置不可选
currentMinDis = INF;
break;
}
currentMinDis = min(currentMinDis, distances[i][j]);
currentSumDis += distances[i][j];
}
// 找到距所有居民点最短距离值最大的垃圾箱位置
if (minDis < currentMinDis && currentMinDis != INF) {
minDis = currentMinDis;
sumDis = currentSumDis;
answer.clear();
answer.push_back(i);
} else if (minDis == currentMinDis && currentMinDis != INF) {
// 找到距所有居民点最短距离之和最小的垃圾箱位置
if (sumDis > currentSumDis) {
answer.clear();
sumDis = currentSumDis;
answer.push_back(i);
}
}
}
if (answer.size() == 0) {
cout << "No Solution" << endl;
} else {
cout << 'G' << *answer.begin() << endl;
printf("%.1lf %.1lf\n", (double)minDis, sumDis/(double)n);
}
return 0;
}
PS:代码写完进行测试的时候,输入测试数据:
纳闷了。。。记得 printf 函数是会进行四舍五入的啊。。。怎么没有四舍五入。。。当时还以为记错了测试数据,再一看测试数据:
比较一下,确实不一样,当时还以为代码写错了,然后输出的时候自己手动加了一个四舍五入的过程。提交的时候最后一个测试点一直过不去。。。后来直觉告诉我应该不是代码的问题。于是把手动加的四舍五入的过程去了,再提交了一,居然过了:
这个。。。我就不知道说什么了。。。想着应该是编译器不同的问题,我本地用的是 DEV-C++ 5.11 版本的 IDE,里面带的是 GCC 4.9.2。PAT 评判系统用的是 G++ 4.7.2 。
除了这个,真想不出别的原因了。。。如果你知道原因,欢迎评论区指出,谢谢。