题目链接:Qin Shi Huang's National Road System
题意
有n个城市,秦始皇要修用n-1条路把它们连起来,要求从任一点出发,都可以到达其它的任意点。秦始皇希望这所有n-1条路长度之和最短。然后徐福突然有冒出来,说是他有魔法,可以不用人力、财力就变出其中任意一条路出来。
秦始皇希望徐福能把要修的n-1条路中最长的那条变出来,但是徐福希望能把要求的人力数量最多的那条变出来。对于每条路所需要的人力,是指这条路连接的两个城市的人数之和。
最终,秦始皇给出了一个公式,A/B,A是指要徐福用魔法变出的那条路所需人力, B是指除了徐福变出来的那条之外的所有n-2条路径长度之和,选使得A/B值最大的那条。
分析:
要使得A/B最大,则B应尽量小,A尽量大。
而这n-2条边的最小值显然会在最小生成树的n-1条边中选出。
注意:这样的最小生成树可能会有多颗,但这并不妨碍我们接下来的计算。
因为我们会对每一条边都考虑一次加入操作,并为了防止在生成树上出现环,再断开在环上边权最大的边,以使得B尽可能小。而这里的A就是被考虑加入生成树上的边连接的两做城池的人口总和。
最终,这样枚举完所有边,并取所有A/B值中的最大值即是答案。
加入一条边,为了任然满足树的要求,必须在树上断开一条边(这里要求B尽可能小,则应断开权值最大的边)。而人口就是加入的边的连接的两座城池人口之和。
下面附上两份代码:
代码1:(在Prim中用优先队列实现每次弹出一个minCost最小的点的编号,wrong answer)
#include <iostream> #include <cstdio> #include <cstring> #include <string> #include <vector> #include <queue> #include <cmath> using namespace std; const double kDNF = 1000000000; const int kMaxN = 1010; struct City{ int x, y, popu; }; int n; City city[kMaxN]; double edge[kMaxN][kMaxN], max_edge[kMaxN][kMaxN]; int pre[kMaxN]; bool used[kMaxN]; double total_cost, min_cost[kMaxN]; struct Node{ int id; Node() {} Node(int id_) : id(id_) {} bool operator < (const Node &n) const{ return min_cost[id] > min_cost[n.id]; } }; void Read(){ scanf("%d", &n); for(int i = 0; i < n; i++) scanf("%d %d %d", &city[i].x, &city[i].y, &city[i].popu); } inline double GetDist(const City &c1, const City &c2){ return sqrt(1.0 *((c1.x - c2.x) * (c1.x - c2.x) + (c1.y - c2.y) * (c1.y - c2.y))); } void MakeEdge(){ for(int i = 0; i < n; i++){ for(int j = 0; j < i; j++){ edge[i][j] = edge[j][i] = GetDist(city[i], city[j]); } edge[i][i] = kDNF; } } void Prim(){ for(int i = 0; i < n; i++){ used[i] = false; min_cost[i] = kDNF; for(int j = 0; j < n; j++){ max_edge[i][j] = 0; } } total_cost = 0; priority_queue<Node> que; min_cost[0] = 0; pre[0] = -1; que.push(Node(0)); while(!que.empty()){ Node now_n = que.top(); que.pop(); if(!used[now_n.id]){ for(int i = 0; i < n; i++){ if(used[i]){ max_edge[now_n.id][i] = max_edge[i][now_n.id] = max(max_edge[i][pre[now_n.id]], min_cost[now_n.id]); }else{ if(min_cost[i] > edge[i][now_n.id]){ min_cost[i] = edge[i][now_n.id]; pre[i] = now_n.id; que.push(Node(i)); } } } used[now_n.id] = true; total_cost += min_cost[now_n.id]; } } } void GetAns(){ double ret = 0; for(int i = 0; i < n; i++){ for(int j = 0; j < i; j++){ ret = max(ret, 1.0*(city[i].popu + city[j].popu) / (total_cost - max_edge[i][j])); } } printf("%.2f\n", ret); } void Solve(){ MakeEdge(); Prim(); GetAns(); } int main(){ int test; scanf("%d", &test); while(test--){ Read(); Solve(); } return 0; }
我的猜测是,在优先队列里的比较函数里的比较值不是Node的成员变量,并且在某个节点编号已经入队的前提下,后续步骤中仍在更新minCost的值,然而入队了的元素的比较函数的minCost不再更新 导致优先队列里的元素排序出错,然后一直wrong的。
代码2:(O(n^2)普通Prim)
#include <iostream> #include <cstdio> #include <cstring> #include <string> #include <vector> #include <cmath> using namespace std; const double kDNF = 1000000000; const int kMaxN = 1010; struct City{ int x, y, popu; }; int n; City city[kMaxN]; double edge[kMaxN][kMaxN], max_edge[kMaxN][kMaxN]; int pre[kMaxN]; bool used[kMaxN]; double total_cost, min_cost[kMaxN]; void Read(){ scanf("%d", &n); for(int i = 0; i < n; i++) scanf("%d %d %d", &city[i].x, &city[i].y, &city[i].popu); } inline double GetDist(const City &c1, const City &c2){ return sqrt(1.0 *((c1.x - c2.x) * (c1.x - c2.x) + (c1.y - c2.y) * (c1.y - c2.y))); } void MakeEdge(){ for(int i = 0; i < n; i++){ for(int j = 0; j < i; j++){ edge[i][j] = edge[j][i] = GetDist(city[i], city[j]); } edge[i][i] = kDNF; } } void Prim(){ for(int i = 0; i < n; i++){ used[i] = false; min_cost[i] = kDNF; for(int j = 0; j < n; j++){ max_edge[i][j] = 0; } } total_cost = 0; min_cost[0] = 0; pre[0] = -1; for(int k = 0; k < n; k++){ int u = -1; for(int v = 0; v < n; v++){ if(!used[v] && (u == -1 || min_cost[u] > min_cost[v])) u = v; } for(int i = 0; i < n; i++){ if(used[i]){ max_edge[u][i] = max_edge[i][u] = max(max_edge[i][pre[u]], min_cost[u]); }else{ if(min_cost[i] > edge[i][u]){ min_cost[i] = edge[i][u]; pre[i] = u; } } } used[u] = true; total_cost += min_cost[u]; } } void GetAns(){ double ret = -1; for(int i = 0; i < n; i++){ for(int j = 0; j < i; j++){ ret = max(ret, 1.0*(city[i].popu + city[j].popu) / (total_cost - max_edge[i][j])); } } printf("%.2f\n", ret); } void Solve(){ MakeEdge(); Prim(); GetAns(); } int main(){ int test; scanf("%d", &test); while(test--){ Read(); Solve(); } return 0; }