【最小乘积生成树】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离AB最远,所以S△ABC面积最大。

向量AB=(B.x - A.x , B.y - A.y)

向量AC= (C.x - A.x , C.y - A.y)

向量AB、AC的叉积(的二分之一)为S△ABC的面积(只不过叉积是有向的,是负的,所以最小化这个值,即为最大化面积)。

最小化:(B.x-A.x)*(C.y-A.y)-(B.y-A.y)*(C.x-A.x)

=(B.x-A.x)*C.y+(A.y-B.y)*C.x  -  A.y*(B.x-A.x)+A.x*(B.y-A.y)/*粗体为常数,不要管*/

所以将每个点的权值修改为 y[i]*(B.x-A.x)+(A.y-B.y)*x[i] 做最小生成树,找到的即是C。】

Step3:递归地分别往AC、BC靠近原点的一侧找。递归边界:该侧没有点了(即叉积大于等于零)。

BZOJ2395 裸题

Code:

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 using namespace std;
 5 int res;
 6 char c;
 7 inline int Get()
 8 {
 9     res=0;c=‘*‘;
10     while(c<‘0‘||c>‘9‘)c=getchar();
11     while(c>=‘0‘&&c<=‘9‘){res=res*10+(c-‘0‘);c=getchar();}
12     return res;
13 }
14 struct Edge{int u,v,c,t,w;void read(){u=Get();v=Get();c=Get();t=Get();}};
15 struct Point{int x,y;Point(const int &A,const int &B){x=A;y=B;}Point(){}};
16 typedef Point Vector;
17 typedef long long LL;
18 Vector operator - (const Point &a,const Point &b){return Vector(a.x-b.x,a.y-b.y);}
19 int Cross(Vector A,Vector B){return A.x*B.y-A.y*B.x;}
20 bool operator < (const Edge &a,const Edge &b){return a.w<b.w;}
21 Edge edges[10001];
22 int n,m,rank[201],fa[201];
23 Point ans=Point(1000000000,1000000000),minc,mint;
24 inline void init()
25 {
26     memset(rank,0,sizeof(rank));
27     for(int i=0;i<n;i++)
28       fa[i]=i;
29 }
30 int findroot(int x)
31 {
32     if(fa[x]==x)
33       return x;
34     int t=findroot(fa[x]);
35     fa[x]=t;
36     return t;
37 }
38 inline void Union(int U,int V)
39 {
40     if(rank[U]<rank[V])
41       fa[U]=V;
42     else
43       {
44         fa[V]=U;
45         if(rank[U]==rank[V])
46           rank[U]++;
47       }
48 }
49 inline Point Kruscal()
50 {
51     int tot=0;
52     Point now=Point(0,0);
53     init();
54     for(int i=1;i<=m;i++)
55       {
56           int U=findroot(edges[i].u),V=findroot(edges[i].v);
57           if(U!=V)
58             {
59                 Union(U,V);
60                 tot++;
61                 now.x+=edges[i].c;
62                 now.y+=edges[i].t;
63                 if(tot==n-1)
64                   break;
65             }
66       }
67     LL Ans=(LL)ans.x*ans.y,Now=(LL)now.x*now.y;
68     if( Ans>Now || (Ans==Now&&now.x<ans.x) )
69       ans=now;
70     return now;
71 }
72 void Work(Point A,Point B)
73 {
74     for(int i=1;i<=m;i++)
75       edges[i].w=edges[i].t*(B.x-A.x)+edges[i].c*(A.y-B.y);
76     sort(edges+1,edges+m+1);
77     Point C=Kruscal();
78     if(Cross(B-A,C-A)>=0)
79       return;
80     Work(A,C);
81     Work(C,B);
82 }
83 int main()
84 {
85     scanf("%d%d",&n,&m);
86     for(int i=1;i<=m;i++)
87       edges[i].read();
88     for(int i=1;i<=m;i++)
89       edges[i].w=edges[i].c;
90     sort(edges+1,edges+m+1);
91     minc=Kruscal();
92     for(int i=1;i<=m;i++)
93       edges[i].w=edges[i].t;
94     sort(edges+1,edges+m+1);
95     mint=Kruscal();
96     Work(minc,mint);
97     printf("%d %d\n",ans.x,ans.y);
98     return 0;
99 }
时间: 2024-10-12 16:03:56

【最小乘积生成树】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 最小乘积生成树

链接: #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))

【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引入两个权值的概念(或是多个权值,一般是两个),看