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

D142 最小生成树 Kruskal 算法+01分数规划 P4951 [USACO01OPEN] Earthquake_哔哩哔哩_bilibili

 

P4951 [USACO01OPEN] Earthquake - 洛谷

选择一些边使得 n 个节点连通,并且要最大化单位时间的利润。

思路

最大化和式的分数,显然是 01 分数规划

设所选的边集为 $S$,考虑二分一个答案 $x$,使得 $\frac{F-\sum{C_i}}{\sum{T_i}}\ge x,\;i\in S$ 成立,即 $F-\sum(x*T_i+C_i)\ge 0$ 成立

将边重新赋权为 $W_i=x*T_i+C_i$,对新边权跑最小生成树求和,如果 $F-sum\ge 0$,则说明答案可以更大

相关板子:

A07【模板】01分数规划 - 董晓 - 博客园

D08【模板】最小生成树 Kruskal 算法 - 董晓 - 博客园

 

// 最小生成树 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);
}

 

posted @ 2026-04-20 16:04  董晓  阅读(62)  评论(0)    收藏  举报