2 条题解

  • 0
    @ 2026-6-14 15:16:07

    // 最小生成树 kruskal+01分数规划 O(m*logm*log(1e9))
    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=405,M=10005;
    const double eps=1e-6;
    int n,m,F,fa[N];
    struct A{int u,v,c,t; double w;}a[M];
    
    int find(int x){
      return fa[x]==x?x:fa[x]=find(fa[x]);
    }
    bool kruskal(double mid){
      for(int i=1;i<=n;i++) fa[i]=i;
      for(int i=1;i<=m;i++) a[i].w=mid*a[i].t+a[i].c;
      sort(a+1,a+m+1,[&](A a,A b){return a.w<b.w;});
      int tot=0; double sum=0;
      for(int i=1;i<=m;i++){
        int x=find(a[i].u),y=find(a[i].v);
        if(x!=y){
          fa[x]=y;
          sum+=a[i].w;
          if(++tot==n-1) break;
        }
      }
      return F-sum>=0;
    }
    int main(){
      scanf("%d%d%d",&n,&m,&F);
      for(int i=1;i<=m;i++){
        scanf("%d%d%d%d",&a[i].u,&a[i].v,&a[i].c,&a[i].t);
      }
      double l=0,r=1e9,mid;
      while(l+eps<r){
        mid=(l+r)/2;
        if(kruskal(mid)) l=mid;
        else r=mid;
      }
      printf("%.4lf\n",l);
    }
    
    • 0
      @ 2026-4-28 11:04:12

      solution:solution:

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

      $ans= \frac{F-\sum{v_i} \quad (i\in S)}{\sum{t_i}\quad (i\in S)}$

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

      F=ansti+viF=ans*\sum{t_i}+\sum{v_i}

      F=ansti+vi(iS)F=\sum{ans*t_i+v_i}\quad _{(i\in S)}

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

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

      1.1. x>ansx>ans

      当我们比率为ans时:Fansti+vi(iK)F\leq \sum{ans*t_i+v_i}\quad {(i\in K )}

      现在我们的比率x比ans还大!那我们的肯定有:F<xti+vi(iK)F< \sum{x*t_i+v_i}\quad {(i\in K)}

      2.2. x=ansx=ans

      我已经说过了:Fansti+vi(iK)F\leq\sum{ans*t_i+v_i}\quad {(i\in K)}

      3.3. x<ansx<ans

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

      F=ansti+vi(iS)F=\sum{ans*t_i+v_i}\quad {(i\in S)}

      此时我们的x<ansx<ans 所以

      F>xti+vi(iS)F>\sum{x*t_i+v_i}\quad {(i\in S)}

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

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

      我们将最小的ansti+vi(iK)\sum{ans*t_i+v_i}\quad {(i\in K)} 看做min

      1. F<minF<min,则必有F<xti+vi(iK)F< \sum{x*t_i+v_i}\quad {(i\in K)} 这不就是第一种情况吗?x必然大于ans
      2. F=minF=min,则必有Fxti+vi(iK)F \leq \sum{x*t_i+v_i}\quad {(i\in K)} 这不就是第二种情况吗?x必然等于ans
      3. F>minF>min,则x必然不大于也不等于ans,那不就是小于ans吗?(上面证过了若x<ans,则必存在一个KF>xti+vi(iK)F>\sum{x*t_i+v_i}\quad {(i\in K)}

      那我们如何求最小的xti+vi(iK)\sum{x*t_i+v_i}\quad {(i\in K)} 呢? 这就是求最小生成树嘛(只不过;树边权变成了ansti+vians*t_i+v_i,我们每一次都重新排个序就行了)!

      总复杂度:O(mlogmlog(rl+1))O(m *logm*log(r-l+1))

      code: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;
      }
      
      
      • 1

      D142 最小生成树 Kruskal 算法+01分数规划[USACO01OPEN] Earthquake

      信息

      ID
      2205
      时间
      1000ms
      内存
      128MiB
      难度
      10
      标签
      递交数
      2
      已通过
      1
      上传者