铁路修复计划
Time limit per test: 2.0 seconds
Time limit all tests: 15.0 seconds
Memory limit: 256 megabytes
在 A 国有很多城际铁路。这些铁路都连接两个城市(城市从 1 到 n 编号),可以双向通行,使得任意两个城市之间都由铁路网联系起来。
不过在一次星球大战之后,所有的铁路都经历了不同程度的损伤以至于无法通行了。由于经费紧缺,A 国政府不愿意再出资造新的铁路。对于原有的城际铁路,根据铁路的实际情况,有以下两种处理办法:
- 使用国内技术进行修复:主要针对损坏情况不是很严重的铁路。国内公司会对铁路状况进行评估,然后如实开出铁路修复的费用。
- 使用国外技术进行修复:主要针对损坏情况严重的铁路。国外公司也会对铁路情况进行评估,然后按照铁路实际修复费用的 k 倍来收费(其中 k 是一个由国外公司决定的实数,不管怎么说,优惠是不可能的,所以 k≥1 )。
A国政府修复铁路的总预算是 M ,目标是要让任意两个城市之间都能通过铁路联系起来。在预算不够且能够完成目标的条件下,显然没必要修复每一条铁路。
国外公司通过不知什么途径了解到了 A 国政府的总预算 M ,他们现在要把 k 定下来,并且希望 k 尽可能得大。但 k 又不能太大,不然,如果 A 国政府发现无法完成任务的话,整个订单都会泡汤。
Input
测试数据包含不超过 30 个测试文件。每个测试文件是单个测试点。
第一行是三个整数 n,m,M (2≤n≤105,n−1≤m≤min{105,n(n−1)2},1≤M≤1015) 。
接下来 m 行,每行四个整数 ui,vi,ti,fi 。表示一条城际铁路,连接着 ui 和 vi 城市, ti 表示铁路实际修复费用。 fi=1 表示只能由国外公司修复, fi=0 表示由国内公司修复。 (1≤ui,vi≤n,ui≠vi,1≤ti≤106,fi∈{0,1}) 。输入保证两个城市之间不会存在多条铁路。
输入保证:
- 在国外公司不乱收费 (k=1) 的情况下,使用预算能完成要求。
- 完全不使用国外技术,只使用国内技术,是不能完成目标的。
Output
求 k 的最大值。输出答案与标准答案相差不超过 10−6 即判为正确。
Examples
3 3 9 1 2 1 1 1 3 2 0 2 3 1 1
7.000000
3 3 9 1 2 1 1 1 3 2 1 2 3 2 1
3.000000
G. 铁路修复计划
二分
k
,然后每次对边重新赋值排序后,就是经典的 MST 问题了。
复杂度
O(nlog2n)
题目中k最小是1,那么现在找一个大一点的数high,k一定是1~high中的某个数。(因为k就是个实数,一定在1~正无穷内,
因此找个大点的数给k一个合适的界限就好)开始二分。找到mid.以当前mid为k值,求出各个边的权,并将其按从小到大
排序。用Kruskal求MST,如果最后求得的MST>M。说明当前mid大了,不能满足要求,则在左区间的进行二分查找,否则,
在右区间进行查找。
初始自己对人家给的思路理解得并不透彻。我以为结束条件还是low<high,发现这样答案和题中给出的输出差距很大。后来
明白了,这个是实数类型的二分查找。我应该尽可能的不停的去二分,才能非常接近某个确定的值。所以二分查找的次数要
尽可能多些。但也要注意别太大了,太大了会导致超时,而且这个次数的上限特别好试探,很容易就可以找出。边权排序运
算符重载要写在结构体内,原来是在外面写了构造函数,并且high是1e10二分次数进行了55次,提交超时两次。后来把运算
符重载写在结构体内,我把high扩大1e15,并且二分次数扩大到70,提交过了,说明在外面写一个cmp排序应该没直接在结构
体写运算符重载快。
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;
const int maxn = 100003;
const double eps = 1e-7;
int father[maxn];
double ans;
int n,m;
double M;
struct Edge
{
int u; ///端点
int v; ///端点
double w; ///权值
int f; ///国内修还是国外修
bool operator < (const Edge &e1) const
{
return w < e1.w;
}
}edge[maxn<<4],tmp[maxn<<4];
///tmp临时存放边的数组
void make_set()
{
for(int i = 1; i <= n; i++)
father[i] = i;
}
int find_set(int x)
{
int root = x;
while(root != father[root]) root = father[root];
while(x != father[x]) {int temp = father[x]; father[x] = root; x = temp;}
return root;
}
void union_set(int x,int y)
{
father[x] = y;
}
int Kruskal(double k) ///假设k值,求MST
{
make_set(); ///不要忘记初始化并查集
for(int i = 0; i < m; i++)
{
tmp[i] = edge[i];
if(tmp[i].f)
tmp[i].w = k*edge[i].w;
}
sort(tmp,tmp+m); ///对这些边进行从小到大排序。
double mst = 0;
int num = 0;
for(int i = 0; i < m; i++)
{
int u = tmp[i].u;
int v = tmp[i].v;
int fu = find_set(u);
int fv = find_set(v);
if(fu != fv)
{
union_set(fu,fv); ///合并集合
num++; ///边数加1
mst = mst + tmp[i].w;
}
if(num == n-1)
break;
}
///最小生成树值比M大。
if(mst - M > eps) return -1;
else return 1;
}
int main()
{
while(~scanf("%d%d%lf",&n,&m,&M)) ///输入城市个数,道路条数,最大钱数
{
for(int i = 0; i < m; i++)
{
scanf("%d%d%lf%d",&edge[i].u,&edge[i].v,&edge[i].w,&edge[i].f);
}
double low,high,mid;
low = 1;
high = 1e15; ///定义的high大一些
/**这个循环次数越多,二分次数越多,答案越精确。发现循环次数少的时候
输出的数和答案的差距很大,题目要求答案差距不能大于1e-6.所以尽量循环
次数多点**/
for(int i = 1; i <= 70; i++)
{
mid = (low+high)/2;
int ans = Kruskal(mid);
if(ans == -1)
high = mid-1;
else
low = mid+1;
}
printf("%lf\n",(low+high)/2);
}
return 0;
}