最小乘积生成树板题。分别将
s
u
m
c
sum_c
sumc和
s
u
m
t
sum_t
sumt当做横坐标与纵坐标,于是对于任意的一个生成树都可以对应到坐标系中的一个点。我们只需要找出
x
∗
y
x*y
x∗y最小的那个点。步骤如下:
首先分别找到
s
u
m
c
sum_c
sumc最小和
s
u
m
t
sum_t
sumt最小的两个点。(下图中的A与B)
然后我们可以发现最优解一定在左下的凸包上。凸包求法如下:
每次找到离直线
A
B
AB
AB距离最远的点
C
C
C,那么这个点一定在凸包上。然后我们可以继续递归求解
A
C
AC
AC和
B
C
BC
BC两个部分了。
对于
C
C
C点,一定是使得三角形
A
B
C
ABC
ABC面积最大的点,可以通过叉积计算。把叉积的式子展开后可以通过修改边的权值求一个最小生成树把
C
C
C求出。具体可以看这里
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e2+10;
const int maxm=1e4+10;
const int oo=6e4+10;
int n,m,fa[maxn];
struct Edge{int u,v,c,t,w;}e[maxm];
struct Point{
int x,y;
Point(int X=0,int Y=0){x=X,y=Y;}
friend inline Point operator+(const Point &a,const Point &b){return Point(a.x+b.x,a.y+b.y);}
friend inline Point operator-(const Point &a,const Point &b){return Point(a.x-b.x,a.y-b.y);}
friend inline int cross(const Point &a,const Point &b){return a.x*b.y-a.y*b.x;}
friend inline ll f(const Point &a){return (ll)a.x*a.y;}
}ans(oo,oo);
inline bool cmp(const Edge &a,const Edge &b){return a.w<b.w;}
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x;
}
inline int getfa(int x){return fa[x]==x?x:fa[x]=getfa(fa[fa[x]]);}
inline Point Kruscal(int cnt=0){
Point ret=(0,0);sort(e+1,e+m+1,cmp);
for(int i=0;i<n;++i) fa[i]=i;
for(int i=1;i<=m;++i){
int U=getfa(e[i].u),V=getfa(e[i].v);
if(U==V) continue;
fa[U]=V,ret=ret+Point(e[i].c,e[i].t),++cnt;
if(cnt==n-1) break;
}if((f(ans)>f(ret))||(f(ans)==f(ret)&&ans.x>ret.x)) ans=ret;
return ret;
}
inline void work(Point L,Point R){
for(int i=1;i<=m;++i) e[i].w=e[i].c*(L.y-R.y)+e[i].t*(R.x-L.x);
Point M=Kruscal();
if(cross(R-L,M-L)>=0) return;
work(L,M),work(M,R);
}
int main(){
//freopen("3778.in","r",stdin);
n=read(),m=read();
for(int i=1;i<=m;++i) e[i]=(Edge){read(),read(),read(),read()};
for(int i=1;i<=m;++i) e[i].w=e[i].c;Point C_min=Kruscal();
for(int i=1;i<=m;++i) e[i].w=e[i].t;Point T_min=Kruscal();
work(C_min,T_min);printf("%d %d\n",ans.x,ans.y);
}