题解 P4951 【[USACO 2001 OPEN]地震】
solution:
这题其实牵扯到了一种普遍的二分答案的方法,与[HNOI2009 最小圈](https://www.luogu.org/problemnew/show/P3199) 一样有许多的共同点。因为这些题要求我们输出的ans都是针对权值总和不断计算而来的。本题就是个经典例题(二分答案求最优比率生成树),经典因为我们可以直接通过题意看出它要求我们输出的**最终答案**实际上就是:
ans= \frac{F-\sum{v_i} \quad (i\in S)}{\sum{t_i}\quad (i\in S)}
(其中S为我们最终所选择的最优生成树的边权的集合)我们将这个式子转换一下,可以得到:
F=ans*\sum{t_i}+\sum{v_i}
F=\sum{ans*t_i+v_i}\quad _{(i\in S)}
注:这一点我们一定要清楚:此时等式右边就是生成树(只不过;树边权变成了
上述式子中我们的ans就是我们最终要输出的最优比率,这个ans是我们可以二分得到的!因为答案具有单调性,当然这个需要我们进一步证明:首先,因为ans是最优比率,那么我们可以知道,对于图上的任意一个生成树的边的集合K,一定有:
1. x>ans :
当我们比率为ans时:
现在我们的比率x比ans还大!那我们的肯定有:
2. x=ans :
我已经说过了:
3. x<ans :
这个时候我们发现我们不能确认
如何二分(为什么用最小生成树):
那我们证明了上面这个东西有什么用呢?根据上述三个关系的特性(就是那三个不等式),我们发现当我们将x带进去后,只要求得最小的
我们将最小的
- 若
F<min ,则必有F< \sum{x*t_i+v_i}\quad {(i\in K)} 这不就是第一种情况吗?x必然大于ans - 若
F=min ,则必有F \leq \sum{x*t_i+v_i}\quad {(i\in K)} 这不就是第二种情况吗?x必然等于ans - 若
F>min ,则x必然不大于也不等于ans,那不就是小于ans吗?(上面证过了若x<ans,则必存在一个KF>\sum{x*t_i+v_i}\quad {(i\in K)} )
那我们如何求最小的
总复杂度:
code:
//耗时 41ms 内存 1036KB
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#define ll long long
#define db double
#define rg register int
using namespace std;
const db cha=1e-9;
struct su{
int x,y,v,t;db k;
}a[10005];
int n,m,f;
int s[405];
inline int qr(){
char ch;
while((ch=getchar())<'0'||ch>'9');
int res=ch^48;
while((ch=getchar())>='0'&&ch<='9')
res=res*10+(ch^48);
return res;
}
inline bool cmp(su x,su y){return x.k<y.k;}
inline int get(int x){
if(s[x]==x)return x;
return s[x]=get(s[x]);
}
inline bool check(db x){
db res=0;
for(rg i=1;i<=n;++i)s[i]=i;//并查集
for(rg i=1;i<=m;++i)
a[i].k=x*a[i].t+(db)a[i].v;//更新边权
sort(a+1,a+m+1,cmp);//kruskal求最小生成树
for(rg i=1;i<=m;++i)
if(get(a[i].x)!=get(a[i].y))
res+=a[i].k,s[get(a[i].x)]=get(a[i].y);//求新边权的总和
return f>res?0:1;
}
int main(){
freopen("quake.in","r",stdin);
freopen("quake.out","w",stdout);
n=qr(),m=qr(),f=qr();
for(rg i=1;i<=m;++i)
a[i]=su{qr(),qr(),qr(),qr()};
if(check(0))puts("0.0000"),exit(0);//特判
db l=0,r=1e14,mid;
while(r-l>cha){//二分答案
mid=(l+r)/2;
if(check(mid))r=mid-cha;
else l=mid+cha;
}printf("%.4lf",l);
return 0;
}