1 条题解
-
0
自动搬运
来自洛谷,原作者为

灯芯糕
草,赶紧先找个坑把自己埋了搬运于
2025-08-24 22:05:35,当前版本为作者最后更新于2019-02-13 15:45:14,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
这题其实牵扯到了一种普遍的二分答案的方法,与[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为我们最终所选择的最优生成树的边权的集合)我们将这个式子转换一下,可以得到:
注:这一点我们一定要清楚:此时等式右边就是生成树(只不过;树边权变成了)
上述式子中我们的ans就是我们最终要输出的最优比率,这个ans是我们可以二分得到的!因为答案具有单调性,当然这个需要我们进一步证明:首先,因为ans是最优比率,那么我们可以知道,对于图上的任意一个生成树的边的集合K,一定有:$\frac{F-\sum{v_i} \quad (i\in K)}{\sum{t_i}\quad (i\in K)}\leq ans $ (根据上述方式转化得:)只有当我们的集合K取到最优的生成树时也就是K=S的时候,这个式子才会是等式!然后我们来证明二分的正确性:当我们二分求ans的时候,对于我们当前的比率x,一定有这三种情况:
:
当我们比率为ans时:
现在我们的比率x比ans还大!那我们的肯定有:
:
我已经说过了:
:
这个时候我们发现我们不能确认 和 的大小关系了!因为我们的K是任意生成树的边的集合,此时我们小于等于大于都有可能,但也正是如此,我们可以证明一定存在一个生成树边的集合满足因为就拿我们ans情况下的最优边集S,因为 此时我们的 所以
如何二分(为什么用最小生成树):
那我们证明了上面这个东西有什么用呢?根据上述三个关系的特性(就是那三个不等式),我们发现当我们将x带进去后,只要求得最小的 我们就可以直接判断出x和ans的关系了!
我们将最小的 看做min
- 若,则必有 这不就是第一种情况吗?x必然大于ans
- 若,则必有 这不就是第二种情况吗?x必然等于ans
- 若,则x必然不大于也不等于ans,那不就是小于ans吗?(上面证过了若x<ans,则必存在一个K)
那我们如何求最小的 呢? 这就是求最小生成树嘛(只不过;树边权变成了,我们每一次都重新排个序就行了)!
总复杂度:
//耗时 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; }
- 1
信息
- ID
- 3985
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者