zoukankan      html  css  js  c++  java
  • [USACO]地震 (二分答案+最优比率生成树详解)

    题面:[USACO 2001 OPEN]地震

    题目描述:

    一场地震把约翰家的牧场摧毁了, 坚强的约翰决心重建家园。 约翰已经重建了N个牧场,现在他希望能修建一些道路把它们连接起来。研究地形之后,约翰发现可供修建的道路有M条。碰巧的是,奶牛们最近也成立一个工程队,专门从事修复道路。而然,奶牛们很有经济头脑,如果无利可图,它们是不会干的。
    奶牛们关注的是挣钱速度,即总利润和总施工时间的比值。约翰和奶牛达成了协议,奶牛负责修建道路,将所有牧场连通,而约翰需要支付F元。每条道路都有自己的施工时间和建造成本。连接两个相同的牧场的道路可能有多条。保证所有的牧场必定是可连通的,不过也有可能一些道路的建造成本之和会超过F。

    请帮助奶牛们选择修复哪些道路,才能使单位时间的利润最大?

    输入格式:

    第一行:三个整数: N,M和F, $ 1 ≤ N ≤ 400 $ , $ 1 ≤ M ≤ 10000 $ , $ 1 ≤ F ≤ 2 × 10^9 $
    第二行到 $ M + 1 $ 行:第 $ i + 1 $ 行表示第i条道路的信息,有四个整数: $ U_i $ , $ V_i $ , $ C_i $ 和 $ T_i $ , $ U_i $ 和 $ V_i $ 表示这条道路连接的牧场编号,Ci表示道路修建的成本,Ti表示道路修建所需要的时间, $ 1 ≤ U_i ≤ N $ , $ 1 ≤ V_i ≤ N $ , $ 1 ≤ C_i ≤ 2 × 10^9 $ , $ 1 ≤ T_i ≤ 2 × 10^9 $

    输出格式:

    第一行:一个保留四位小数的浮点数,表示奶牛们能挣到的最大单位时间利润,如果奶牛们无钱可赚,则输出0.0000

    输入样例#1:

    5 5 100
    1 2 20 5
    1 3 20 5
    1 4 20 5
    1 5 20 5
    2 3 23 1

    输出样例#1:

    1.0625

    说明:

    (奶牛们可以选择连通最后四条道路,则总时间为 16,总成本为 83,所以单位利润为17/16 = 1.0625)

    $ solution: $

    这题其实牵扯到了一种普遍的二分答案的方法,与我等下会讲的HNOI2009 最小圈 一样有许多的共同点。因为这些题要求我们输出的ans都是针对权值总和不断计算而来的。本题就是个经典例题(二分答案求最优比率生成树),因为我们可以直接通过题意看出它要求我们输出的最终答案实际上就是:

    $ ans= frac{F-sum{v_i} quad (iin S)}{sum{t_i}quad (iin S)} $

    (其中S为我们最终所选择的最优生成树的边权的集合)我们将这个式子转换一下,可以得到:

    $ F=ans imes sum{t_i}+sum{v_i} $

    $ F=sum{ans imes t_i+v_i}quad _{(iin S)} $

    注:这一点我们一定要清楚:此时等式右边就是生成树(只不过;树边权变成了 $ ans imes t_i+v_i $ )

    上述式子中我们的ans就是我们最终要输出的最优比率,这个ans是我们可以二分得到的!因为答案具有单调性,当然这个需要我们进一步证明:首先,因为ans是最优比率,那么我们可以知道,对于图上的任意一个生成树的边的集合K,一定有: $ frac{F-sum{v_i} quad (iin K)}{sum{t_i}quad (iin K)}leq ans $ (根据上述方式转化得: $ Fleq sum{ans imes t_i+v_i}quad {(iin K)} $ )只有当我们的集合K取到最优的生成树时也就是K=S的时候,这个式子才会是等式!然后我们来证明二分的正确性:当我们二分求ans的时候,对于我们当前的比率x,一定有这三种情况:

    $ 1. $ $ x>ans $ :

    当我们比率为ans时: $ Fleq sum{ans imes t_i+v_i}quad {(iin K )} $

    现在我们的比率x比ans还大!那我们的肯定有: $ F< sum{x imes t_i+v_i}quad {(iin K)} $

    $ 2. $ $ x=ans $ :

    我已经说过了: $ Fleqsum{ans imes t_i+v_i}quad {(iin K)} $

    $ 3. $ $ x<ans $ :

    这个时候我们发现我们不能确认 $ F $ 和 $ sum{ans imes t_i+v_i}quad {(iin K)} $ 的大小关系了!因为我们的K是任意生成树的边的集合,此时我们小于等于大于都有可能,但也正是如此,我们可以证明一定存在一个边的集合满足 $ F>sum{x imes t_i+v_i}quad {(iin S)} $ 因为就拿我们ans情况下的最优边集S,因为 $ F=sum{ans imes t_i+v_i}quad {(iin S)} $ 此时我们的 $ x<ans $ 所以 $ F>sum{x imes t_i+v_i}quad {(iin S)} $

    如何二分(为什么用最小生成树):

    那我们证明了上面这个东西有什么用呢?根据上述三个关系的特性(就是那三个不等式),我们发现当我们将x带进去后,只要求得最小的 $ sum{ans imes t_i+v_i}quad {(iin K)} $ 我们就可以直接判断出x和ans的关系了!

    我们将最小的 $ sum{ans imes t_i+v_i}quad {(iin K)} $ 看做min

    1. 若 $ F<min $ ,则必有 $ F< sum{x imes t_i+v_i}quad {(iin K)} $ 这不就是第一种情况吗?x必然大于ans

    2. 若 $ F=min $ ,则必有 $ F leq sum{x imes t_i+v_i}quad {(iin K)} $ 这不就是第一种情况吗?x必然等于ans

    3. 若 $ F>min $ ,则x必然不大于也不等于ans,那不就是小于ans吗?(证过了若x<ans,则必存在一个K $ F>sum{x imes t_i+v_i}quad {(iin K)} $ )

    那我们如何求最小的 $ sum{x imes t_i+v_i}quad {(iin K)} $ 呢? 这就是求最小生成树嘛(只不过;树边权变成了 $ ans imes t_i+v_i $ ,我们每一次都重新排个序就行了)!

    总复杂度: $ O(m imes logm imes log(r-l+1)) $

    $ code: $

    #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;
    }
    
    
  • 相关阅读:
    使用Fiddler工具在夜神模拟器或手机上抓包
    typedef & #defiine & struct
    int main (int argc, const char * argv[0]) 中参数的含义;指针数组和数组指针
    sql语句查询结果合并union all用法_数据库技巧
    jsp html 实现隐藏输入框,点击可以取消隐藏&&弹出输入框
    php弹出确认框
    mysql 插入string类型变量时候,需要注意的问题,妈的,害我想了好几个小时!!
    PHP页面跳转传值的三种常见方式
    Ubuntu&Mac下使用alias简化日常操作
    php mysql 中文乱码解决,数据库显示正常,php调用不正常
  • 原文地址:https://www.cnblogs.com/812-xiao-wen/p/10367014.html
Copyright © 2011-2022 走看看