山山赚钱记 二分+图论
【题目描述】
又有老师让山山做事情了,不过这次的任务虽然有时体力活,但是山山做的心甘情愿,
为什么呢?因为有 money 可以拿啦~啦啦啦,山山好开心啊好开心~~
这次的任务是让山山 去铺水管,学校一共有 N 个中转点,有 M 条可供选择的水管道
路,一条水管道路连接两个中转点,最后 山山铺完的水管必须保证任意两个中转点之间都
可以互相送水(直接相连或间接相连都可以) ,并且所铺的水管数尽量少。铺每条水管道
路有不同的报酬,也需要耗不同大小的体力,由于山山喜欢 money 但是不喜欢动,所以他
希望他平均每单位的体力所赚到的钱最多。
【输入格式】
第一行两个整数 N,M,表示有 N 个中转点和 M 条可供选择的水管道路。
下面 M 行,每行四个整数,第 i+1 行的 u,v,w,c,表示点 u 到点 v 间铺水
管可得报酬 w 元,需要耗费 c 单位的体力
【输出格式】
一行一个实数,表示平均每单位体力最多赚多少钱。保留 4 位小数。
【样例】
money.in
5 5
1 2 20 5
1 3 20 5
1 4 20 5
1 5 20 5
2 3 23 1
money.out
5.1875
【数据规模】
100% N<=400, M<=10000
这题用到了最小化平均值的思想和结论。
首先我们二分,然后我们考虑怎么check,我们可以跑最大生成树,在保证边数的同时又可以check出是否符合条件。
我们传过去一个mid值,然后我们知如果满足条件的话:sum(wi)/sum(ci)>=mid
所以我们把式子转化成0>=sum(wi)-mid*sum(ci)
于是我们把每条边的边权修改为wi-mid*ci,然后排序,跑克鲁斯卡尔,最后看看是否满足条件即可.
代码:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define ll long long #define il inline #define db double using namespace std; il int gi() { int x=0,y=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') y=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*y; } int n,m; db eps=0.000001; struct edge { int from,to; int w,c; db nice; }e[100045]; bool cmp(edge a,edge b) { return a.nice>b.nice; } int fa[100045]; int find(int x) { if(fa[x]!=x) fa[x]=find(fa[x]); return fa[x]; } il bool check(db mid) { for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<=m;i++) e[i].nice=(db)e[i].w-((db)e[i].c*mid); sort(e+1,e+1+m,cmp); int sum=0; db s=0; for(int i=1;i<=m;i++) { int r1=find(e[i].from),r2=find(e[i].to); if(r1!=r2) { fa[r2]=r1; sum++; s+=e[i].nice; } if(sum==n-1) break; } if(s>=0) return 1; else return 0; } int main() { freopen("money.in","r",stdin); freopen("money.out","w",stdout); n=gi(),m=gi(); for(int i=1;i<=n;i++) fa[i]=i; int x,y,u,v; for(int i=1;i<=m;i++) { x=gi(),y=gi(),u=gi(),v=gi(); e[i].from=x; e[i].to=y; e[i].w=u; e[i].c=v; } db l=0,r=100000; db ans; while(r-l>=eps) { db mid=(l+r)/2; if(check(mid)) { ans=mid; l=mid; } else r=mid; } printf("%.4f\n",ans); return 0; }
PEACE