bzoj 2395 [Balkan 2011]Timeismoney (最小乘积生成树)

本文探讨了一种特殊的图论问题,即通过选取特定的边集合来连接n个城市,目标是最小化费用和时间的乘积。文章提供了一个详细的算法实现,包括Kruskal算法的应用及如何通过坐标轴和几何概念寻找最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://www.elijahqi.win/archives/2812
Description

 有n个城市(编号从0..n-1),m条公路(双向的),从中选择n-1条边,使得任意的两个城市能够连通,一条边需要的c的费用和t的时间,定义一个方案的权值v=n-1条边的费用和*n-1条边的时间和,你的任务是求一个方案使得v最小

Input

第一行两个整数n,m,接下来每行四个整数a,b,c,t,表示有一条公路从城市a到城市b需要t时间和费用c
Output

【output】timeismoney.out
仅一行两个整数sumc,sumt,(sumc表示使得v最小时的费用和,sumc表示最小的时间和) 如果存在多个解使得sumc*sumt相等,输出sumc最小的
Sample Input

5 7
0 1 161 79
0 2 161 15
0 3 13 153
1 4 142 183
2 4 236 80
3 4 40 241
2 1 65 92

Sample Output

279 501

HINT

【数据规模】

1<=N<=200

1<=m<=10000

0<=a,b<=n-1

0<=t,c<=255

有5%的数据m=n-1

有40%的数据有t=c

对于100%的数据如上所述

非常好的题解:https://www.cnblogs.com/autsky-jadek/p/3959446.html

求sigma(t)*sigma(c)最小 那么将sigma(t)=B,sigma(c)=A

假设有一棵生成树 然后将t,c作为坐标轴上的(x,y)看成一个点

那么 可以看成是选择这些点构成的一个下凸包上的一个点 具体证明:看上方链接

那么 相当于我需要求一个距离A,B这条直线最远的点 假设为点C 可以用叉积来算

即(B.x-A.x)(C.y-A.y)-(C.x-A.x)(B.y-A.y)

然后关键的一步

将这步式子转化为(B.x-A.x)C.y+(A.y-B.y)*C.x-A.y(B.x-A.x)+A.x*(B.y-A.y)

观察到前半部分我们可以算一下比例乘进去继续kruskal递归做下去 后半部分是常数不用管 然后就做完了

#include<cstdio>
#include<algorithm>
#define ll long long
#define M 11000
#define N 220
using namespace std;
inline char gc(){
    static char now[1<<16],*S,*T;
    if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=gc();}
    while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=gc();
    return x*f;
}
struct node{
    int c,t,w,x,y;
}data[M];
struct P{
    int x,y;
}point[N],ans;
int fa[N],n,m;
inline bool cmp(const node &a,const node &b){return a.w<b.w;}
inline int find(int x){while(x!=fa[x]) x=fa[x]=fa[fa[x]];return x;}
inline void init(){for (int i=1;i<=n;++i) fa[i]=i;}
inline P kruskal(){
    init();int tot=0;P pt=(P){0,0};
    for (int i=1;i<=m;++i){
        int x=find(data[i].x),y=find(data[i].y);
        if (x!=y){pt.x+=data[i].c;pt.y+=data[i].t;fa[x]=y;if (++tot==n-1) break;}
    }
    ll aa=(ll)ans.x*ans.y,now=(ll)pt.x*pt.y;
    if (now<aa||now==aa&&pt.x<ans.x) ans=pt;
    return pt;
}
inline void work(P &a,P &b){
    for (int i=1;i<=m;++i) data[i].w=data[i].t*(b.x-a.x)+data[i].c*(a.y-b.y);
    sort(data+1,data+m+1,cmp);P c=kruskal();
    if ((b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y)>=0) return;
    work(a,c);work(c,b);
}
int main(){
    freopen("bzoj2395.in","r",stdin);
    n=read();m=read();ans.x=1e9;ans.y=1e9;
    for (int i=1;i<=m;++i){
        data[i].x=read()+1;data[i].y=read()+1;data[i    ].c=read();data[i].t=read();data[i].w=data[i].c;
    }sort(data+1,data+m+1,cmp);P a=kruskal();
    for (int i=1;i<=m;++i) data[i].w=data[i].t;
    sort(data+1,data+m+1,cmp);P b=kruskal();work(a,b);
    printf("%d %d\n",ans.x,ans.y);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值