bzoj2395 Timeismoney【最小乘积生成树】

解题思路:

题目即是求令 citi 最小的生成树。

我们把 ci 看做横坐标 x ti看做纵坐标 y ,要求k=xy最小,即是使得函数 y=kx 最接近坐标轴。

而一个点对应了一个函数,这些函数要么重合,要么不相交。
所以我们要找的点一定在一个左下部分的凸包上。

看网上的题解求凸包左下部分都是用的分治法。

1.首先分别求出 ci 最小的和 ti 最小的点为 A,B (这个就是普通的MST),然后再求出离线段 AB 最远的点 C (靠近原点一侧);

2.然后递归下去分别求离AC,CB最远的点,直到没有更远点 C

3.最后整个左下凸包上的点就都求出来了。

如何求离线段最远的点呢? 用面积法。

AB最远的点 C 肯定使SΔABC面积最大,即是让 AB×AC (负数)最小,而
AB×AC=(B.xA.x)(C.yA.y)(B.yA.y)(C.xA.x)
=C.x(A.yB.y)+C.y(B.xA.x)+()
() 是只与 A,B 有关的量,已被确定,所以我们只要令与 C 有关的部分最小,那么就把所有边权赋值为ci(A.yB.y)+ti(B.xA.x),然后求一遍最小生成树就得到了C点。

时间复杂度大概是 O(log2n) ,很玄学啊,不过还是很快的。

#include<bits/stdc++.h>
#define ll long long
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
    if(c=='-')f=-1;
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

const int N=205,M=10005;
struct edge
{
    int x,y,t,c,val;
    inline friend bool operator < (const edge &a,const edge &b)
    {return a.val<b.val;}
}bian[M];
struct point 
{
    int x,y;
    point(){}
    point(int _x,int _y):x(_x),y(_y){}
    inline friend point operator - (const point &a,const point &b)
    {return point(a.x-b.x,a.y-b.y);}
    inline friend ll operator * (const point &a,const point &b)
    {return 1ll*a.x*b.y-1ll*a.y*b.x;}
}ans;
int n,m,fa[N];

int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}

point kruskal()
{
    for(int i=1;i<=n;i++)fa[i]=i;
    sort(bian+1,bian+m+1);
    int k=0;point p=point(0,0);
    for(int i=1;i<=m;i++)
    {
        int x=find(bian[i].x),y=find(bian[i].y);
        if(x!=y)
        {
            k++;fa[y]=x;
            p.x+=bian[i].c,p.y+=bian[i].t;
            if(k==n-1)break;
        }
    }
    if(1ll*p.x*p.y<1ll*ans.x*ans.y||1ll*p.x*p.y==1ll*ans.x*ans.y&&p.x<ans.x)ans=p;
    return p;
}

void solve(point A,point B)
{
    int x=B.x-A.x,y=A.y-B.y;
    for(int i=1;i<=m;i++)bian[i].val=bian[i].c*y+bian[i].t*x;
    point C=kruskal();
    if((B-A)*(C-A)>=0)return;
    solve(A,C),solve(C,B);
}

int main()
{
    //freopen("lx.in","r",stdin);
    n=getint(),m=getint();
    for(int i=1;i<=m;i++)
    {
        bian[i].x=getint()+1,bian[i].y=getint()+1;
        bian[i].c=getint(),bian[i].t=getint();
    }
    ans=point(1e9,1e9);
    for(int i=1;i<=m;i++)bian[i].val=bian[i].c;
    point A=kruskal();
    for(int i=1;i<=m;i++)bian[i].val=bian[i].t;
    point B=kruskal();
    solve(A,B);
    cout<<ans.x<<" "<<ans.y;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值