【BOI2011】timeismoney (最小乘积生成树)

6 篇文章 0 订阅
4 篇文章 0 订阅

Description

NetLine 公司想要给N 个城镇提供宽带网络。为此,需要建造一个有N -1 条镇间宽带链接的网络,拥有一条消息能在这个网络上从任意镇传到任意镇的性质。NetLine 已经鉴定了所有城镇对之间能够直接建立的链接。对于每个这样的可能链接,他们知道建造这个链接的费用和时间。

公司对使建造总时间(链接不能同时建造)和总费用最小化都感兴趣。因为他们不能决定要单独使用哪一个标准,所以他们决定采用如下公式计算一个网络的评估值:

SumTime = 建造选择的链接所花时间之和

SumMoney = 建造选择的链接所花金钱之和

V = SumTime * SumMoney

选择一些需要建造的链接,使得所建网络的评估值V 最小。

Input

输入的第一行包含整数N——城镇的个数和M——能够建造的链接数。城镇从0 到N - 1 编号。

接下来M 行中每一行含四个整数x, y, t 和c——意味着城镇x 可以耗费t 时间及c 费用与城镇y 建立链接。

Output

输出的第一行为两个数字:最优方案(那个评估值V 最小的)使用的总时间(SumTime)和总费用(SumMoney),用一个空格隔开。接下来N - 1 行描述要建造的链接。每行包含一对数字(x, y)描述一个需建造的链接(须在输入中描述的可建造链接内)。这些数对可以按任意顺序输出。

当有多个最优解存在时,你可以输出其中任意一个。

Sample Input

5 7

0 1 161 79

0 2 161 15

0 3 13 153

1 4 142 183

2 4 236 80

3 4 40 241

2 1 65 92

Sample Output

279 501

2 1

0 3

0 2

3 4

Data Constraint

• 1 <= N <= 200

• 1 <= M <= 10 000

• 0 <= x, y <= N - 1

• 1 <= t, c <= 255

• 一个测试点有M = N - 1

• 40% 的数据对于每个可建造链接有t = c

The Solution

题目大意

给定我们一些边与每条边上的两个权值xi,yi,连成一幅无向连通图,求在图中找一棵最小生成树,使得$\sum_x_i * \sum_y_i $取最小值。

Analysis

就是让我们求最小乘积生成树

这不就摆明了是道裸体嘛QQ~~,
直接裸奔就好了

下面来普及~~(口胡)~~一下最小乘积生成树

就拿这道题为例来说吧

我们可以把每条边的权值描述为一个二元组 ( x i , y i ) (xi,yi) xi,yi,把生成树转化为平面内的点,然后把它投影到一个平面直角坐标系上,横坐标表示 ∑ x i \sum{x_i} xi ,纵坐标表示 ∑ y i \sum{y_i} yi

则问题转化为求一个点,使得 x y = k xy=k xy=k最小,换句话说,就是使得过这个点的反比例函数 y = k x y=\dfrac{k}{x} y=xk最接近坐标轴。

因此我们需要求出所有这些点构成的凸包的左下部分,从中找一个最大的。

接着我们就切入正题

1、先求出分别距x轴和y轴最近的生成树(点):A,B
实际操作可以分别按x权值和y权值做最小生成树。

怎么求凸包的左下部分呢?
用分治法!!!
分治大法好!
分治大法好!
分治大法好!

2、我们可以寻找一个在AB的靠近原点的一侧且离AB最远的点C(生成树)。递归分治更新答案

怎么找C点呢?

由于C离AB最远,所以S△ABC面积最大。

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

向量 A C = ( C . x − A . x , C . y − A . y ) 向量AC= (C.x - A.x , C.y - A.y) 向量AC=(C.xA.x,C.yA.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 ) (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) (B.xA.x)(C.yA.y)(B.yA.y)(C.xA.x)=(B.xA.x)C.y+(A.yB.y)C.xA.y(B.xA.x)+A.x(B.yA.y)

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

至于方案数的话,开个数组记录一下就好了。

CODE

#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#define fo(i,a,b) for (int i=a;i<=b;i++)
#define fd(i,a,b) for (int i=a;i>=b;i--) 
#define N 10005
#define INF 1 << 30

using namespace std;

typedef long long ll;

struct Edge
{
	int from,to,c,t;
	ll z;
}E[N];

struct Node
{
	int x,y;
}Minx,Miny,Ans;

int Res[N][3];

int n,m;
int Dad[N];

int read(int &n)
{
	char ch = ' ';
	int q = 0, w = 1;
	for (;(ch != '-') && ((ch < '0') || (ch> '9'));ch = getchar());
	if (ch == '-') w = -1,ch = getchar();
	for (; ch >= '0' && ch <= '9';ch = getchar()) q = q * 10 + ch - 48;
	n = q * w;
	return n;
}

int Get(int x)
{
	if (Dad[x] == x) return x;
	else return Dad[x] = Get(Dad[x]);
}

bool cmp1(Edge a,Edge b)
{
	return a.t < b.t;
}
bool cmp2(Edge a,Edge b)
{
	return a.c < b.c;
}
bool cmp3(Edge a,Edge b)
{
	return a.z < b.z;
}

Node Kruskal()
{
	int tot = 0;
	Node G = {0,0};
	static int Mark[N][3];
	memset(Mark,0,sizeof(Mark));
	fo(i,1,n) Dad[i] = i;
	fo(i,1,m)
	{
		int xx = Get(E[i].from),
			yy = Get(E[i].to);
		if (xx != yy)
		{
			Dad[xx] = yy;
			tot ++;
			G.x += E[i].t;
			G.y += E[i].c;
			Mark[++ Mark[0][0]][1] = E[i].from - 1;
			Mark[Mark[0][0]][2] = E[i].to - 1; 
			if (tot == n - 1) break;
		}
	}
	ll t1 = (ll)Ans.x * Ans.y,
	   t2 = (ll)G.x * G.y;
	if (t2 < t1 || (t2 == t1 && G.x < Ans.x)) 
	{
		memcpy(Res,Mark,sizeof(Mark));
		Ans = G;
	}
	return G;
}

ll Chaji(Node A,Node B,Node C)
{
	return ((ll)B.x - A.x) * ((ll)C.y - A.y ) - ((ll)B.y - A.y) * ((ll)C.x - A.x);
}

void Work(Node a,Node b)
{
	fo(i,1,m)
		E[i].z = E[i].c * (b.x - a.x) + E[i].t * (a.y - b.y);
	sort(E + 1,E + m + 1,cmp3);
	Node mid = Kruskal();
	if (Chaji(a,b,mid) >= 0) return;
	Work(a,mid);
	Work(mid,b);
}

int main()
{
	freopen("timeismoney.in","r",stdin);
	freopen("timeismoney.out","w",stdout);
	Ans.x = Ans.y = INF;
	read(n),read(m);
	fo(i,1,m) 
	{
		int u,v;
		read(u),read(v),read(E[i].t),read(E[i].c);
		E[i].from = ++ u;
		E[i].to = ++ v;	
	} 
	sort(E + 1,E + m + 1,cmp1);
	Minx = Kruskal();
	sort(E + 1,E + m + 1,cmp2);
	Miny = Kruskal();
	Work(Minx,Miny);
	printf("%d %d\n",Ans.x,Ans.y);
	fo(i,1,Res[0][0]) printf("%d %d\n",Res[i][1],Res[i][2]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值