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.x−A.x,B.y−A.y)
向量 A C = ( C . x − A . x , C . y − A . y ) 向量AC= (C.x - A.x , C.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 ) (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)
所以将每个点的权值修改为 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.x−A.x)+(A.y−B.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;
}