【BZOJ2395】【Balkan 2011】Timeismoney 最小乘积生成树

链接:

#include <stdio.h>
int main()
{
    puts("转载请注明出处[辗转山河弋流歌 by 空灰冰魂]谢谢");
    puts("网址:blog.csdn.net/vmurder/article/details/46828379");
}

题解:

裸最小乘积生成树。

最小乘积生成树定义:

有一张n个点m条边的无向图,每条边有k个权值。

现在要取一个边集M使得其将所有点连通,并使

∏ki=1(∑j∈Mjcost(j,vali)) 最小

即个边集的每一种边权的总和的乘积最小。

比如:

k=1时,就是裸最小生成树。

k=2时,就是要使 [边集的权值1的和]*[边集的权值2的和] 最小。

最小乘积生成树的一种求法:

广义上的说法(没必要看,或者看完下面的再来看这个就好)

首先我们可以把每种生成树想成一个k维的点,第i维的坐标即那一维上权值的和。

然后我们可以先求出每一维坐标最小的一棵生成树(裸上最小生成树就好),

然后得到一个k-1维的面,然后我们来求一下离这个面最远的点,然后分治下去……据说期望很快……

二维最小乘积生成树的求法:

给每一棵生成树都定义两个权值X、Y,其中X为其包含的所有边的权值x的和,Y为其包含的所有边的权值y的和,那么我们可以把每一种生成树看成一个坐标。

我们先求出坐标x最小的一棵生成树,再求出坐标y最小的一棵生成树。

然后我们可以考虑,最优的点一定在下凸包上【证明一】,然后我们要进行一个不断向左下拓展点的过程:对于两个点A、B形成的直线,我们可以找出在这条直线左下的最远的点C,然后对AC、CB递归做同样的过程,直到找不到一个在左下的点C为止。

然后如何找一个最远的点C呢?

我们可以发现既然有一条边固定,那么不妨把“最远”转化成三角形面积最大,这个用叉积搞一搞,,然后会推出公式面积跟点C有关的部分= 常数A?x+常数B?y ,,那么我们把所有边的权值 x 乘上 A ,权值 y 乘上 B 就好了,,,A、B 是啥自己求去!

【题外】:三维的,就是离一个平面最远,转化成体积最大,递归分成三层而非两层,,,,,然后四维,甚至更高维,感觉应该是同理的吧?

关于上文中【证明一】

每个点(xi,yi) 都对应一条函数曲线 ki=xi?yi,而任意两不同 ki ,它们的函数曲线是不交的(有交的话则存在一点 (xj,yj) 使得 ki=xj?yj=kj而ki!=kj 成立,显然这是悖论),那么显然最优点肯定不会在凸包内,否则必有凸包上一点比它优。

那么会不会求出这个某种意义上的凸包后,最优点在凸包外,却没被找到呢?

不会。

若有这种情况,此点必然在凸包上某边的左下方,,然后一定会被找出来。。

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 205
#define M 10100
#define inf 0x3f3f3f3f
using namespace std;
struct Eli
{
    int u,v,a,b,c;
    void read()
    {
        scanf("%d%d%d%d",&u,&v,&a,&b);
        u++,v++;
    }
}e[M];
inline bool cmpa(const Eli &a,const Eli &b){return a.a<b.a;}
inline bool cmpb(const Eli &a,const Eli &b){return a.b<b.b;}
inline bool cmpc(const Eli &a,const Eli &b){return a.c<b.c;}
struct Point
{
    int x,y;
    void print(){printf("%d %d\n",x,y);}
    Point(int _x=0,int _y=0):x(_x),y(_y){}
    bool operator < (const Point &A)const
    {
        unsigned int p=  x;p*=  y;
        unsigned int q=A.x;q*=A.y;
        return p==q?x<A.x:p<q;
    }
}ans,now,mina,minb;
int f[N],n,m;
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
Point Kruscal()
{
    int i,fa,fb;
    now=Point(0,0);
    for(i=1;i<=n;i++)f[i]=i;
    for(i=1;i<=m;i++)
    {
        fa=find(e[i].u),fb=find(e[i].v);
        if(fa!=fb)
        {
            f[fb]=fa;
            now.x+=e[i].a;
            now.y+=e[i].b;
        }
    }
    if(now<ans)ans=now;
    return now;
}
inline int xmul(const Point &A,const Point &B,const Point &C)
{return (C.y-A.y)*(B.x-A.x)-(C.x-A.x)*(B.y-A.y);}
void work(const Point &A,const Point &B)
{
    for(int i=1;i<=m;i++)
        e[i].c=e[i].b*(A.x-B.x)+e[i].a*(B.y-A.y);
    sort(e+1,e+m+1,cmpc);
    Point C=Kruscal();
    if(xmul(A,B,C)<=0)return ;
    work(A,C),work(C,B);
}
int main()
{
//  freopen("test.in","r",stdin);

    int i,j,k;
    int a,b,c;
    ans=Point(inf,inf);

    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++)e[i].read();
    sort(e+1,e+m+1,cmpa),mina=Kruscal();
    sort(e+1,e+m+1,cmpb),minb=Kruscal();
    work(minb,mina),ans.print();

    fclose(stdin);
    fclose(stdout);
    return 0;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-07 19:14:25

【BZOJ2395】【Balkan 2011】Timeismoney 最小乘积生成树的相关文章

bzoj2395[Balkan 2011]Timeismoney最小乘积生成树

所谓最小乘积生成树,即对于一个无向连通图的每一条边均有两个权值xi,yi,在图中找一颗生成树,使得Σxi*Σyi取最小值. 直接处理问题较为棘手,但每条边的权值可以描述为一个二元组(xi,yi),这也就不难想到将生成树转化为平面内的点,x代表Σxi,y代表Σyi(注意这里的xi,yi指的是在生成树中的边的权值),那么问题就变成了在平面内找一个点使得x*y最小,那么显然这个点是在下凸壳上的. 因此可以首先找出两个一定在凸包上的点,例如A(minx,y),B(miny,x),在直线AB下方找一个在凸

bzoj2395 [Balkan 2011]Timeismoney

题意:每条边有两个权值a,b,求图的最小二元和乘积生成树(即该树的sum_a*sum_b最小). 标程: 1 #include<bits/stdc++.h> 2 #define P pair<ll,ll> 3 #define fir first 4 #define sec second 5 using namespace std; 6 typedef long long ll; 7 int read() 8 { 9 int x=0,f=1;char ch=getchar(); 10

【最小乘积生成树】bzoj2395[Balkan 2011]Timeismoney

设每个点有x,y两个权值,求一棵生成树,使得sigma(x[i])*sigma(y[i])最小. 设每棵生成树为坐标系上的一个点,sigma(x[i])为横坐标,sigma(y[i])为纵坐标.则问题转化为求一个点,使得xy=k最小.即,使过这个点的反比例函数y=k/x最接近坐标轴. Step1:求得分别距x轴和y轴最近的生成树(点):A.B(分别按x权值和y权值做最小生成树即可). Step2:寻找一个在AB的靠近原点一侧的且离AB最远的生成树C,试图更新答案. [怎么找???? ——由于C离

【BZOJ 2395】 [Balkan 2011]Timeismoney

2395: [Balkan 2011]Timeismoney Time Limit: 10 Sec Memory Limit: 128 MB Submit: 304 Solved: 169 [Submit][Status][Discuss] Description 有n个城市(编号从0..n-1),m条公路(双向的),从中选择n-1条边,使得任意的两个城市能够连通,一条边需要的c的费用和t的时间,定义一个方案的权值v=n-1条边的费用和*n-1条边的时间和,你的任务是求一个方案使得v最小 Inp

HDU5697 刷题计划 dp+最小乘积生成树

分析:就是不断递归寻找靠近边界的最优解 学习博客(必须先看这个): 1:http://www.cnblogs.com/autsky-jadek/p/3959446.html 2:http://blog.csdn.net/u013849646/article/details/51524748 注:这里用的最小乘积生成树的思想,和dp结合 每次找满足条件的最优的点,只不过BZOJ裸题的满足条件是形成一棵树 这个题是大于m,生成树借用最小生成树进行求解最优,大于m用dp进行求解最优 #include

二维最小乘积生成树学习小记

Preface 对于形如给定一些边,其边权为xi和yi,构造一个生成树,使得 我们称这棵树,为最小乘积生成树.我们可以考虑,沿用最小生成树的思想,把这种新颖的最小生成树做对. Content 算法简介 其实就是利用树形结合的思想,将点弄到平面直角坐标系上,使之明了,转化问题,求出最优解. 算法应用 应对类似的裸题,直接裸奔 算法核心 我们将sigma(xi)看成横坐标,sigma(yi)看成纵坐标,在坐标系中绘制出来.并在x,y轴作垂线,构成一个正方形 我们要求一种方案xy=k最小,也就是这个正

树(最小乘积生成树,克鲁斯卡尔算法):BOI timeismoney

The NetLine company wants to offer broadband internet to N towns. For this, it suffices to constructa network of N-1 broadband links between the towns, with the property that a message can travelfrom any town to any other town on this network. NetLin

LuoguP5540:【模板】最小乘积生成树(几何逼近)

题意:给定N点,M边,每条边有两个属性(a,b),现在让你选N-1条边出来,然后使得∑a*∑b最小.N<200,M<1e4: 思路:我们把∑a看成x,∑b看成y,那么一个方案对应一个二维坐标(x,y).假设我知道了其中两个方案[A,B],那么,如果另外一个方案C更优,则在二维平面上,C至少要满足在A和B的左边.然后[A,C],[C,B]继续下推. 这个有点像凸包的逼近,所以复杂度和凸包上的点数有关,其理论点数是sqrt(lnN)的.所以总的复杂度趋近于NlogN*sqrt(lnN): #inc

bzoj3571: [Hnoi2014]画框 最小乘积匹配+最小乘积XX总结,

思路大概同bzoj2395(传送门:http://www.cnblogs.com/DUXT/p/5739864.html),还是将每一种匹配方案的Σai看成x,Σbi看成y,然后将每种方案转化为平面上的点,再用km去找最远的点就行了. 然而几个月前就学过km且到现在还未写过一道km的题的我并不知道km如何对于负权给出最优解.... #define XX 某传统算法(例如:最小生成树,二分图最优带权匹配什么的) 顺便总结一下最小乘积XX 即对于XX引入两个权值的概念(或是多个权值,一般是两个),看