0%

最小乘积生成树

给定 \(n\) 个点 \(m\) 条边的无向图,每条边有两个权值 \(a_i,b_i\),求一棵生成树使得 \[ \left(\sum\limits_{e\in T} a_e\right)\left(\sum\limits_{e\in T} b_e\right) \] 最小,输出乘积最小时的 \(\sum\limits_{e\in T} a_e\)\(\sum\limits_{e\in T} b_e\)\(n\leqslant 200\)\(m\leqslant 10000\)\(0\leqslant a_i,b_i\leqslant 255\)\(a_i,b_i\in N\)

对于任意一棵生成树,我们把其映射到平面直角坐标系中的一个点 \((\sum a_e,\sum b_e)\),那么我们所求即为 \(xy\) 最小的点。我们不难发现它一定在所有点构成的左下凸壳上。

我们先分别令 \(a_e=0\)\(b_e=0\),这样求出的是只有一种权值时的最小生成树,故其横/纵坐标一定最小,所以这两个点一定在凸包上(更精确的说,左下凸壳的两个端点)。

然后,我们考虑所有还可能在凸壳上的点,设刚才确定的两个点为 \(A,B\),则如果一个点 \(C\) 可能成为凸壳上的点,其一定在 \(AB\) 的左下方。同时,在 \(AB\) 左下方距离 \(AB\) 最远的点一定在凸壳上,所以我们考虑求出这个距离 \(AB\) 最远的点 \(C\)

如图所示(其实每个点都应该是整点,我没画好),我们现在已知 \(A,B\) 的坐标,想要求出这个点 \(C\),换句话说,我们要找到那个使得 \(\triangle ABC\) 的面积最大的点。假设 \(A,B,C\) 的坐标分别为 \((x_A,y_A),(x_B,y_B),(x_C,y_C)\),则 \(S_{\triangle ABC}=-\dfrac{1}{2}(\overrightarrow{AB}\times \overrightarrow{AC})\) ,故我们需要让 \(\overrightarrow{AB}\times \overrightarrow{AC}\) 最小。下面我们来推一下式子: \[ \begin{aligned} &\overrightarrow{AB}\times \overrightarrow{AC}\\ =&(x_B-x_A)(y_C-y_A)-(x_C-x_A)(y_B-y_A)\\ =&x_By_C-x_By_A-x_Ay_C+x_Ay_A-x_Cy_B+x_Cy_A+x_Ay_B-x_Ay_A\\ =&(x_B-x_A)y_C+(y_A-y_B)x_C-x_By_A+x_Ay_B \end{aligned} \] 后两项是常数,所以我们要让 \((x_B-x_A)y_C+(y_A-y_B)x_C\) 最小,只需要对每条边 \(e\),将 \(v_e\) 设为 \((y_A-y_B)a_e+(x_B-x_A)b_e\),然后求最小生成树得到的那棵树即为点 \(C\)。这里可能有多个点 \(C\) 在同一条直线的情况,但是并不重要。

然后,我们看刚才那张图,\(D,E\) 也是凸壳上的点,所以我们要对 \(AC,BC\) 递归做下去,直到当前直线左下方没有点,也就是叉积大于等于 \(0\),最后取所有点的最小值。

考虑分析复杂度,刚才那个过程实际上是一个叫做 quick hall 的求凸包算法。不难发现,我们一共做了 \(O(凸壳上的点数)\) 次最小生成树。由于所有生成树对应的点都是整点,而且点数远远高于值域范围 \(na\),据 EI 说此时凸壳上的点数最大是 \(O((na)^{\frac{2}{3}})\) 的,所以总复杂度为 \(O(n^{\frac{8}{3}}a^{\frac{2}{3}})\)(prim 实现 MST)。

顺便说两句废话,如果边权不一定为整数但是非负,也可以证明做 MST 的次数是 \(O(m^2)\) 的;如果边权可能为负,那么这个应该没有多项式做法。

代码(比较懒所以写了 kruskal 多一个 \(\log\)):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,fa[205];
ll ans=3e9,ansx,ansy;
struct edge{int s,t,a,b,v;}e[10005];
bool cmp(edge x,edge y){return x.v<y.v;}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void mst(ll &rx,ll &ry)
{
rx=ry=0;
for(int i=1;i<=n;i++)fa[i]=i;
sort(e+1,e+m+1,cmp);
for(int i=1;i<=m;i++)
{
int fx=find(e[i].s),fy=find(e[i].t);
if(fx==fy)continue;
rx+=e[i].a,ry+=e[i].b,fa[fx]=fy;
}
if(rx*ry<ans)ans=rx*ry,ansx=rx,ansy=ry;
}
void solve(ll ax,ll ay,ll bx,ll by)
{
for(int i=1;i<=m;i++)e[i].v=(ay-by)*e[i].a+(bx-ax)*e[i].b;
ll rx,ry;
mst(rx,ry);
if((ay-by)*rx+(bx-ax)*ry-bx*ay+ax*by<0)solve(ax,ay,rx,ry),solve(rx,ry,bx,by);
}
int main()
{
ll ax,ay,bx,by;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)scanf("%d%d%d%d",&e[i].s,&e[i].t,&e[i].a,&e[i].b),e[i].s++,e[i].t++,e[i].v=e[i].a;
mst(ax,ay);
for(int i=1;i<=m;i++)e[i].v=e[i].b;
mst(bx,by);
solve(ax,ay,bx,by);
printf("%lld %lld",ansx,ansy);
return 0;
}