- 有一张\(n\)个点\(m\)条边的无向图,每条边有两种权值\(u_i,v_i\)。
- 求这张图的一个生成树,最小化\(\sum u\times\sum v\)。
- \(n\le200,m\le10^4\)
构造坐标系
听说这是一种套路,然而我从未接触过。。。
考虑我们把\(\sum u\)和\(\sum v\)看作二维坐标\((x,y)\),那么就可以构造出一张平面直角坐标系。
那么现在我们想找的就是平面直角坐标系上\(x\times y\)最小的点。
首先,我们分别找到离\(y\)轴和\(x\)轴最近的两点\(A,B\),这可以通过分别对\(u,v\)两种权值各做一次最小生成树求出。
显然\(A,B\)并不一定是最优点。考虑如果存在一个\(C\)比\(A,B\)更优,一个必要条件就是\(C\)位于直线\(AB\)的左下方(充要条件应该是在反比例函数左下方,但那样就麻烦多了)。
因此,我们去找到离\(AB\)最远的\(C\),这就充要于找到使\(\triangle ABC\)面积最大的\(C\)。
利用叉积公式:
\[2S_{\triangle ABC}=\vec{AC}\times \vec {AB} \]把它化开得到:
\[\begin{align} 2S_{\triangle ABC}&=(x_C-x_A)\times (y_B-y_A)-(y_C-y_A)\times (x_B-x_A)\\ &=x_C\times (y_B-y_A)-y_C\times (x_B-x_A)-(x_A\times (y_B-y_A)-y_A\times (x_B-x_A)) \end{align} \]后面那一坨都是定值,因此我们要最大化\(S_{\triangle ABC}\)就是要最大化\(x_C\times (y_B-y_A)-y_C\times (x_B-x_A)\)。
方便起见,我们可以改成最小化\(x_C\times(y_A-y_B)+y_C\times(x_B-x_A)\)。
然后考虑\(x_C=\sum u,y_C=\sum v\),故上式等同于\(\sum(u\times(y_A-y_B)+v\times(x_B-xA))\)。
那么只要对\(u\times(y_A-y_B)+v\times(x_B-xA)\)做一次最小生成树就好了。
注意,此时的\(C\)仍不一定是最优点,还要递归处理\(AC,CB\)。
至于复杂度,我不会证明,但据说是有保证的。
代码:\(O(\)能过\()\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200
#define M 10000
#define LL long long
using namespace std;
int n,m,ee,lnk[N+5];struct line {int x,y,u,v;}s[M+5];
struct edge
{
int p,x,y,v;I edge(CI i=0,CI a=0,CI b=0,CI c=0):p(i),x(a),y(b),v(c){}
I bool operator < (Con edge& o) Con {return v<o.v;}
}e[M+5];
struct P {int x,y;I P(CI a=0,CI b=0):x(a),y(b){}};
int f[N+5];I int fa(CI x) {return f[x]^x?f[x]=fa(f[x]):x;}//并查集
int ta,tb;LL ans;I P MST()//最小生成树求出点坐标
{
RI i,x,y,a=0,b=0;LL t=0;for(i=1;i<=n;++i) f[i]=i;//初始化并查集
for(sort(e+1,e+m+1),i=1;i<=m;++i)//Kruskal最小生成树
(x=fa(e[i].x))^(y=fa(e[i].y))&&(f[y]=x,t+=e[i].v,a+=s[e[i].p].u,b+=s[e[i].p].v);
1LL*a*b==ans?a<ta&&(ta=a,tb=b):1LL*a*b<ans&&(ta=a,tb=b,ans=1LL*a*b);return P(a,b);//求出点坐标,更新答案
}
I void Solve(Con P& A,Con P& B)//递归求解
{
RI i,a=0,b=0;for(i=1;i<=m;++i) e[i]=edge(i,s[i].x,s[i].y,(A.y-B.y)*s[i].u+(B.x-A.x)*s[i].v);//三角形面积最大的点
P C=MST();1LL*(C.x-A.x)*(B.y-A.y)-1LL*(C.y-A.y)*(B.x-A.x)>0&&(Solve(A,C),Solve(C,B),0);//如果能找到左下方的点,就递归做
}
int main()
{
RI i;for(scanf("%d%d",&n,&m),ans=1e18,i=1;i<=m;++i)
scanf("%d%d%d%d",&s[i].x,&s[i].y,&s[i].u,&s[i].v),++s[i].x,++s[i].y;
for(i=1;i<=m;++i) e[i]=edge(i,s[i].x,s[i].y,s[i].u);P A=MST();//离y轴最近点
for(i=1;i<=m;++i) e[i]=edge(i,s[i].x,s[i].y,s[i].v);P B=MST();//离x轴最近点
return Solve(A,B),printf("%d %d\n",ta,tb),0;
}