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

题意

有n个点和m条边,每条边有两个属性s和t,求一棵生成树,使得选出的n-1条边的sigma(s)*sigma(t)最小。
n 200 , m 10000 , s , t 255

分析

最小乘积生成树模板题。
把每一棵生成树看做平面上的一个点(x,y),其中x等于sigma(s),y等于sigma(t)。
那么可能成为答案的点一定是平面上的一个下凸壳。
考虑先求出x最小和y最小的两棵生成树,这两棵树对应的点一定是凸壳的左右端点,然后考虑分治。
对于当前的两个端点st和ed,找到在这两个点连成的线段下方且距离该直线最远的点,显然这个点也一定在凸壳上,然后往两边继续分治下去就好了。
怎么找最远的点呢?
显然我们是要让这三个点组成的三角形面积最大,也就是AC和AB的叉积尽量大。
那么就是要最大化 ( c . x a . x ) ( b . y a . y ) ( c . y a . y ) ( b . x a . x )
展开后等于 c . x ( b . y a . y ) + c . y ( a . x b . x ) + a . x ( a . y b . y ) + a . y ( b . x a . x )
后面两项显然是常数,那么我们现在要最大化前面两项,只要把每条边的边权设为 s ( b . y a . y ) + t ( a . x b . x ) 然后做kruskal算法就好了。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

typedef long long LL;

const int N=205;

int n,m,f[N];
struct edge{int x,y,s,t,w;}e[10005];
struct point
{
    int x,y;

    bool operator == (const point &d) const {return x==d.x&&y==d.y;}
};
struct data
{
    LL w;int x,y;

    bool operator < (const data &d) const {return w<d.w||w==d.w&&x<d.x||w==d.w&&x==d.x&&y<d.y;}
};

int find(int x)
{
    if (f[x]==x) return x;
    else return f[x]=find(f[x]);
}

bool cmp(edge a,edge b)
{
    return a.w<b.w;
}

point kruskal()
{
    for (int i=1;i<=n;i++) f[i]=i;
    point ans=(point){0,0};
    std::sort(e+1,e+m+1,cmp);
    for (int i=1;i<=m;i++)
    {
        int x=find(e[i].x),y=find(e[i].y);
        if (x!=y) f[x]=y,ans.x+=e[i].s,ans.y+=e[i].t;
    }
    return ans;
}

data solve(point a,point b)
{
    for (int i=1;i<=m;i++) e[i].w=-e[i].s*(b.y-a.y)-e[i].t*(a.x-b.x);
    point c=kruskal();
    if (c==a||c==b||(LL)(c.x-a.x)*(b.y-a.y)-(LL)(c.y-a.y)*(b.x-a.x)<=0) return std::min((data){(LL)a.x*a.y,a.x,a.y},(data){(LL)b.x*b.y,b.x,b.y});
    return std::min(solve(a,c),solve(c,b));
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d%d",&e[i].x,&e[i].y,&e[i].s,&e[i].t);
        e[i].x++;e[i].y++;
    }
    for (int i=1;i<=m;i++) e[i].w=e[i].s;
    point st=kruskal();
    for (int i=1;i<=m;i++) e[i].w=e[i].t;
    point ed=kruskal();
    data ans=solve(st,ed);
    printf("%d %d\n",ans.x,ans.y);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_33229466/article/details/80499047