最小乘积生成树。我们把每棵生成树看做二维平面上的一个点(x,y),x坐标为边权1的和,y坐标为边权2的和。那么我们就是试图找到一个点,使得过它的双曲线最靠近坐标轴。我们不难证明,这样的点一定是在下凸壳上的。于是我们现在就是要找出所有在下凸壳上的点,用他们的x*y来更新答案。
我们先找出最接近x轴和最接近y轴的两个点A,B(即一维上的MST),然后考虑在AB靠近原点一侧找一点C,使得离AB最远,即最大化三角形ABC的面积。面积我们用AB与AC的叉积来表示。可见叉积的符号应该是负的,因此我们就是要最小化AB与AC的叉积。而AB与AC的叉积为:(B.x−A.x)∗(C.y−A.y)−(C.x−A.x)∗(B.y−A.y),
即(B.x−A.x)∗C.y+(A.y−B.y)∗C.x−A.y∗(B.x−A.x)+A.x∗(B.y−A.y)
因此我们可以把每条边的权值改成:(A.y-B.y)*C.x,(B.x-A.x)*C.y,做MST就是C点。然后递归的去AC,BC的一侧接着找。直到不存在这样的点为止(即叉积>=0了)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 210
#define M 10010
inline char gc(){
static char buf[1<<16],*S,*T;
if(S==T){T=(S=buf)+fread(buf,1,1<<16,stdin);if(S==T) return EOF;}
return *S++;
}
inline int read(){
int x=0,f=1;char ch=gc();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=gc();}
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
return x*f;
}
int n,m,fa[N];
struct edge{
int x,y,c,t,w;
friend bool operator<(edge a,edge b){return a.w<b.w;}
}e[M];
struct P{
int x,y;
P(int _x,int _y){x=_x;y=_y;}
friend ll delta(P a,P b){return (ll)a.x*b.y-(ll)a.y*b.x;}
}ans(inf,inf);
inline int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
inline P Kruskal(){
sort(e+1,e+m+1);for(int i=1;i<=n;++i) fa[i]=i;int tot=0;P res(0,0);
for(int i=1;i<=m;++i){
int xx=find(e[i].x),yy=find(e[i].y);
if(xx==yy) continue;fa[xx]=yy;++tot;
res.x+=e[i].c;res.y+=e[i].t;if(tot==n-1) break;
}ll tmp=(ll)res.x*res.y,pre=(ll)ans.x*ans.y;
if(tmp<pre||tmp==pre&&res.x<ans.x) ans=res;
return res;
}
inline void solve(P A,P B){
for(int i=1;i<=m;++i) e[i].w=e[i].t*(B.x-A.x)+e[i].c*(A.y-B.y);
P C=Kruskal();if(delta(P(B.x-A.x,B.y-A.y),P(C.x-A.x,C.y-A.y))>=0) return;
solve(A,C);solve(C,B);
}
int main(){
// freopen("a.in","r",stdin);
n=read();m=read();
for(int i=1;i<=m;++i) e[i].x=read()+1,e[i].y=read()+1,e[i].c=read(),e[i].t=read();
for(int i=1;i<=m;++i) e[i].w=e[i].c;P A=Kruskal();
for(int i=1;i<=m;++i) e[i].w=e[i].t;P B=Kruskal();solve(A,B);
printf("%d %d\n",ans.x,ans.y);
return 0;
}