解题思路:
题目即是求令 ∑ci∗∑ti 最小的生成树。
我们把
∑ci
看做横坐标
x
,
而一个点对应了一个函数,这些函数要么重合,要么不相交。
所以我们要找的点一定在一个左下部分的凸包上。
看网上的题解求凸包左下部分都是用的分治法。
1.首先分别求出 ∑ci 最小的和 ∑ti 最小的点为 A,B (这个就是普通的MST),然后再求出离线段 AB 最远的点 C (靠近原点一侧);
2.然后递归下去分别求离
3.最后整个左下凸包上的点就都求出来了。
如何求离线段最远的点呢? 用面积法。
离
AB→×AC→=(B.x−A.x)∗(C.y−A.y)−(B.y−A.y)∗(C.x−A.x)
=C.x∗(A.y−B.y)+C.y∗(B.x−A.x)+(……)
(……)
是只与
A,B
有关的量,已被确定,所以我们只要令与
C
有关的部分最小,那么就把所有边权赋值为
时间复杂度大概是 O(所得凸包点数∗log2n) ,很玄学啊,不过还是很快的。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int getint()
{
int i=0,f=1;char c;
for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
if(c=='-')f=-1;
for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
return i*f;
}
const int N=205,M=10005;
struct edge
{
int x,y,t,c,val;
inline friend bool operator < (const edge &a,const edge &b)
{return a.val<b.val;}
}bian[M];
struct point
{
int x,y;
point(){}
point(int _x,int _y):x(_x),y(_y){}
inline friend point operator - (const point &a,const point &b)
{return point(a.x-b.x,a.y-b.y);}
inline friend ll operator * (const point &a,const point &b)
{return 1ll*a.x*b.y-1ll*a.y*b.x;}
}ans;
int n,m,fa[N];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
point kruskal()
{
for(int i=1;i<=n;i++)fa[i]=i;
sort(bian+1,bian+m+1);
int k=0;point p=point(0,0);
for(int i=1;i<=m;i++)
{
int x=find(bian[i].x),y=find(bian[i].y);
if(x!=y)
{
k++;fa[y]=x;
p.x+=bian[i].c,p.y+=bian[i].t;
if(k==n-1)break;
}
}
if(1ll*p.x*p.y<1ll*ans.x*ans.y||1ll*p.x*p.y==1ll*ans.x*ans.y&&p.x<ans.x)ans=p;
return p;
}
void solve(point A,point B)
{
int x=B.x-A.x,y=A.y-B.y;
for(int i=1;i<=m;i++)bian[i].val=bian[i].c*y+bian[i].t*x;
point C=kruskal();
if((B-A)*(C-A)>=0)return;
solve(A,C),solve(C,B);
}
int main()
{
//freopen("lx.in","r",stdin);
n=getint(),m=getint();
for(int i=1;i<=m;i++)
{
bian[i].x=getint()+1,bian[i].y=getint()+1;
bian[i].c=getint(),bian[i].t=getint();
}
ans=point(1e9,1e9);
for(int i=1;i<=m;i++)bian[i].val=bian[i].c;
point A=kruskal();
for(int i=1;i<=m;i++)bian[i].val=bian[i].t;
point B=kruskal();
solve(A,B);
cout<<ans.x<<" "<<ans.y;
return 0;
}