【题目】
原题地址
有
n
n
个城市(编号从),
m
m
条公路(双向的),从中选择条边,使得任意的两个城市能够连通,一条边需要的
c
c
的费用和的时间,定义一个方案的权值
v=(n−1
v
=
(
n
−
1
条边的费用和)
∗
∗
条边的时间和),你的任务是求一个方案使得
v
v
最小
【题目分析&学习】
假装自己学了一波东西:最小乘积生成树。
以下转自:dalao的BLOG
最小乘积生成树定义
有一张个点
m
m
条边的无向图,每条边有个权值。
现在要取一个边集M使得其将所有点连通,并使
∏ki=1(∑j∈Mjcost(j,vali))
∏
i
=
1
k
(
∑
j
j
∈
M
c
o
s
t
(
j
,
v
a
l
i
)
)
最小
即个边集的每一种边权的总和的乘积最小。
比如:
k=1
k
=
1
时,就是裸最小生成树。
k=2
k
=
2
时,就是要使 [边集的权值1的和]*[边集的权值2的和] 最小。
最小乘积生成树的一种求法:
广义上的说法:
首先我们可以把每种生成树想成一个
k
k
维的点,第维的坐标即那一维上权值的和。
然后我们可以先求出每一维坐标最小的一棵生成树(裸上最小生成树就好),
然后得到一个
k−1
k
−
1
维的面,然后我们来求一下离这个面最远的点,然后分治下去……据说期望很快……
二维最小乘积生成树的求法:
给每一棵生成树都定义两个权值
X、Y
X
、
Y
,其中
X
X
为其包含的所有边的权值的和,
Y
Y
为其包含的所有边的权值的和,那么我们可以把每一种生成树看成一个坐标。
我们先求出坐标
x
x
最小的一棵生成树,再求出坐标最小的一棵生成树。
然后我们可以考虑,最优的点一定在下凸包上【证明一】,然后我们要进行一个不断向左下拓展点的过程:对于两个点
A、B
A
、
B
形成的直线,我们可以找出在这条直线左下的最远的点
C
C
,然后对递归做同样的过程,直到找不到一个在左下的点
C
C
为止。
然后如何找一个最远的点C呢?——由于C离AB最远,所以S△ABC面积最大。就是让最小
(...) ( . . . ) 是只与 A,B A , B 有关的量,可以看做常数。
因此我们将所有边权设为 a[k].x∗(A.y−B.y)+a[k].y∗(B.x−A.x) a [ k ] . x ∗ ( A . y − B . y ) + a [ k ] . y ∗ ( B . x − A . x ) ,然后求最小生成树就行了。
边界 AB→×AC→≥0 A B → × A C → ≥ 0
【证明一】
每个点
(xi,yi)
(
x
i
,
y
i
)
都对应一条函数曲线
ki=xi?yi
k
i
=
x
i
?
y
i
,而任意两不同
ki
k
i
,它们的函数曲线是不交的(有交的话则存在一点
(xj,yj)
(
x
j
,
y
j
)
使得
ki=xj?yj=kj
k
i
=
x
j
?
y
j
=
k
j
而
ki!=kj
k
i
!
=
k
j
成立,显然这是悖论),那么显然最优点肯定不会在凸包内,否则必有凸包上一点比它优。
那么会不会求出这个某种意义上的凸包后,最优点在凸包外,却没被找到呢?
不会。
若有这种情况,此点必然在凸包上某边的左下方,然后一定会被找出来。。
【参考代码】
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF=1e9;
const int N=205;
const int M=1e5+5;
int n,m;
int fa[N];
struct Tway
{
int x,y,c,t;
LL v;
};
Tway e[M];
struct Tpoint
{
LL x,y;
};
Tpoint ans,A,B;
bool cmp(Tway X,Tway Y)
{
return X.v<Y.v;
}
int findf(int x)
{
return fa[x]==x?x:fa[x]=findf(fa[x]);
}
Tpoint Better(Tpoint X,Tpoint Y)
{
if((X.x*X.y==Y.x*Y.y && X.x>Y.x) || X.x*X.y>Y.x*Y.y)
return Y;
return X;
}
Tpoint Kruscal()
{
Tpoint O=(Tpoint){0,0};
sort(e+1,e+m+1,cmp);
for(int i=1;i<=n;++i)
fa[i]=i;
int now=1;
for(int i=1;i<=m;++i)
{
int fx=findf(e[i].x),fy=findf(e[i].y);
if(fx==fy)
continue;
fa[fx]=fy;
O.x+=e[i].c;O.y+=e[i].t;++now;
if(now==n)
break;
}
ans=Better(ans,O);
return O;
}
LL cross(Tpoint a,Tpoint b,Tpoint c)
{
return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
void solve(Tpoint X,Tpoint Y)
{
LL tx=Y.x-X.x,ty=X.y-Y.y;
for(int i=1;i<=m;++i)
e[i].v=1ll*e[i].c*ty+1ll*e[i].t*tx;
Tpoint O=Kruscal();
if(cross(O,X,Y)>=0)
return;
solve(X,O);
solve(O,Y);
}
int main()
{
// freopen("BZOJ2395.in","r",stdin);
// freopen("BZOJ2395.out","w",stdout);
ans.x=ans.y=INF;
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].c,&e[i].t);
++e[i].x;++e[i].y;e[i].v=e[i].c;
}
A=Kruscal();
for(int i=1;i<=m;++i)
e[i].v=e[i].t;
B=Kruscal();
solve(A,B);
printf("%lld %lld\n",ans.x,ans.y);
return 0;
}
不知道高维的情况应该怎样写呢?貌似还没有这种题出来呢。