题目链接https://www.luogu.org/problem/P5540
题意
给出一个
n
n
n 个点
m
m
m 条边的无向图,第
i
i
i 条边有两个权值
a
i
a_i
ai和
b
i
b_i
bi
求该图的一棵生成树
T
T
T ,使得(
∑
a
e
)
∗
(
∑
b
e
)
\sum a_e)*(\sum b_e)
∑ae)∗(∑be)最小。
题解
把 ∑ a e \sum a_e ∑ae看作 x x x, ∑ b e \sum b_e ∑be看作 y y y,答案就变成了平面上的一个坐标点 ( x , y ) (x,y) (x,y)。这样平面上最多一共有 n n − 2 n^{n-2} nn−2个点(生成树的个数),现在要求出一个点使得 x × y x\times y x×y最小。很容易想到答案在凸包的左下角上,现在就是想办法求出该点。
如果点 A A A是 x x x最小的点, B B B是 y y y最小的点,那么点 A A A和点 B B B一定是在凸包上。假设点 C C C是在 A B AB AB的左下处,那点 C C C一定更优,所以我们要找到点一个在 A B AB AB左下处的点 C C C使得三角形 A B C ABC ABC面积最大,或者 A B × A C AB\times AC AB×AC最小。这样求出来的点 C C C也是在凸包上的。
推一下式子: A B × A C = ( x B − x A ) y C + ( y A − y B ) x C − ( x B − x A ) y A + ( y B − y A ) x A AB\times AC=(x_B-x_A)y_C+(y_A-y_B)x_C-(x_B-x_A)y_A+(y_B-y_A)x_A AB×AC=(xB−xA)yC+(yA−yB)xC−(xB−xA)yA+(yB−yA)xA
后面两项是常数,所以我们只要让 ( x B − x A ) y C + ( y A − y B ) x C (x_B-x_A)y_C+(y_A-y_B)x_C (xB−xA)yC+(yA−yB)xC最小即可。对此我们只要对每条边设一个权值为: ( x B − x A ) a [ i ] + ( y A − y B ) b [ i ] (x_B-x_A)a[i]+(y_A-y_B)b[i] (xB−xA)a[i]+(yA−yB)b[i],跑一边最小生成树就可以求出点 C C C。
求出来点 C C C后,只需要递归求在 A C AC AC和 C B CB CB左下方的点即可。
因为一张 n n n个点的图,凸包上的点数期望是 ln n \sqrt{\ln n} lnn,我们最多有 n n − 2 n^{n-2} nn−2个点,所以最后的复杂度是 O ( m l o g ( m ) ln n n − 2 ) O(mlog(m)\sqrt{\ln n^{n-2}}) O(mlog(m)lnnn−2)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4e5+7;
struct Edge{
int u,v,a,b,w;
bool operator<(const Edge k)const{return w<k.w;}
}e[N];
int n,m;
struct Point{
int x,y;
Point operator - (const Point k)const{return (Point){x-k.x,y-k.y};}
}ans;
int cross(Point k1,Point k2){return k1.x*k2.y-k2.x*k1.y;}
int fa[N];
int fd(int x){return x==fa[x]?x:fa[x]=fd(fa[x]);}
Point kruskal(){
Point res=(Point){0,0};
sort(e+1,e+1+m);
for(int i=0;i<n;i++) fa[i]=i;
for(int i=1;i<=m;i++){
int u=e[i].u,v=e[i].v;
int fu=fd(u),fv=fd(v);
if(fu==fv) continue;
fa[fu]=fv;
res.x+=e[i].a;
res.y+=e[i].b;
}
ll now=1ll*ans.x*ans.y;
ll tmp=1ll*res.x*res.y;
if(now>tmp||(now==tmp&&ans.x>res.x)) ans=res;
return res;
}
void solve(Point A,Point B){
for(int i=1;i<=m;i++) e[i].w=(A.y-B.y)*e[i].a+(B.x-A.x)*e[i].b;
Point C=kruskal();
if(cross(B-A,C-A)>=0) return;
solve(A,C);solve(C,B);
}
int main()
{
ans.x=ans.y=256*200;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].a,&e[i].b);
}
for(int i=1;i<=m;i++) e[i].w=e[i].a;
Point A=kruskal();
for(int i=1;i<=m;i++) e[i].w=e[i].b;
Point B=kruskal();
solve(A,B);
printf("%d %d\n",ans.x,ans.y);
}