bzoj2395 [Balkan2011]Timeismoney(Kruskal+计算几何)

本文介绍了一种寻找加权图中最小乘积生成树(MPST)的方法,通过将生成树视作二维平面上的点(x,y),并利用几何特性优化搜索过程,最终找到使边权乘积最小的生成树。

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

最小乘积生成树。我们把每棵生成树看做二维平面上的一个点(x,y),x坐标为边权1的和,y坐标为边权2的和。那么我们就是试图找到一个点,使得过它的双曲线最靠近坐标轴。我们不难证明,这样的点一定是在下凸壳上的。于是我们现在就是要找出所有在下凸壳上的点,用他们的x*y来更新答案。

我们先找出最接近x轴和最接近y轴的两个点A,B(即一维上的MST),然后考虑在AB靠近原点一侧找一点C,使得离AB最远,即最大化三角形ABC的面积。面积我们用AB与AC的叉积来表示。可见叉积的符号应该是负的,因此我们就是要最小化AB与AC的叉积。而AB与AC的叉积为:(B.xA.x)(C.yA.y)(C.xA.x)(B.yA.y),
(B.xA.x)C.y+(A.yB.y)C.xA.y(B.xA.x)+A.x(B.yA.y)
因此我们可以把每条边的权值改成:(A.y-B.y)*C.x,(B.x-A.x)*C.y,做MST就是C点。然后递归的去AC,BC的一侧接着找。直到不存在这样的点为止(即叉积>=0了)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 210
#define M 10010
inline char gc(){
    static char buf[1<<16],*S,*T;
    if(S==T){T=(S=buf)+fread(buf,1,1<<16,stdin);if(S==T) 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>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x*f;
}
int n,m,fa[N];
struct edge{
    int x,y,c,t,w;
    friend bool operator<(edge a,edge b){return a.w<b.w;}
}e[M];
struct P{
    int x,y;
    P(int _x,int _y){x=_x;y=_y;}
    friend ll delta(P a,P b){return (ll)a.x*b.y-(ll)a.y*b.x;}
}ans(inf,inf);
inline int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
inline P Kruskal(){
    sort(e+1,e+m+1);for(int i=1;i<=n;++i) fa[i]=i;int tot=0;P res(0,0);
    for(int i=1;i<=m;++i){
        int xx=find(e[i].x),yy=find(e[i].y);
        if(xx==yy) continue;fa[xx]=yy;++tot;
        res.x+=e[i].c;res.y+=e[i].t;if(tot==n-1) break;
    }ll tmp=(ll)res.x*res.y,pre=(ll)ans.x*ans.y;
    if(tmp<pre||tmp==pre&&res.x<ans.x) ans=res;
    return res;
}
inline void solve(P A,P B){
    for(int i=1;i<=m;++i) e[i].w=e[i].t*(B.x-A.x)+e[i].c*(A.y-B.y);
    P C=Kruskal();if(delta(P(B.x-A.x,B.y-A.y),P(C.x-A.x,C.y-A.y))>=0) return;
    solve(A,C);solve(C,B);
}
int main(){
//  freopen("a.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=m;++i) e[i].x=read()+1,e[i].y=read()+1,e[i].c=read(),e[i].t=read();
    for(int i=1;i<=m;++i) e[i].w=e[i].c;P A=Kruskal();
    for(int i=1;i<=m;++i) e[i].w=e[i].t;P B=Kruskal();solve(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、付费专栏及课程。

余额充值