题意
有n个点和m条边,每条边有两个属性s和t,求一棵生成树,使得选出的n-1条边的sigma(s)*sigma(t)最小。
分析
最小乘积生成树模板题。
把每一棵生成树看做平面上的一个点(x,y),其中x等于sigma(s),y等于sigma(t)。
那么可能成为答案的点一定是平面上的一个下凸壳。
考虑先求出x最小和y最小的两棵生成树,这两棵树对应的点一定是凸壳的左右端点,然后考虑分治。
对于当前的两个端点st和ed,找到在这两个点连成的线段下方且距离该直线最远的点,显然这个点也一定在凸壳上,然后往两边继续分治下去就好了。
怎么找最远的点呢?
显然我们是要让这三个点组成的三角形面积最大,也就是AC和AB的叉积尽量大。
那么就是要最大化
展开后等于
后面两项显然是常数,那么我们现在要最大化前面两项,只要把每条边的边权设为
然后做kruskal算法就好了。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
typedef long long LL;
const int N=205;
int n,m,f[N];
struct edge{int x,y,s,t,w;}e[10005];
struct point
{
int x,y;
bool operator == (const point &d) const {return x==d.x&&y==d.y;}
};
struct data
{
LL w;int x,y;
bool operator < (const data &d) const {return w<d.w||w==d.w&&x<d.x||w==d.w&&x==d.x&&y<d.y;}
};
int find(int x)
{
if (f[x]==x) return x;
else return f[x]=find(f[x]);
}
bool cmp(edge a,edge b)
{
return a.w<b.w;
}
point kruskal()
{
for (int i=1;i<=n;i++) f[i]=i;
point ans=(point){0,0};
std::sort(e+1,e+m+1,cmp);
for (int i=1;i<=m;i++)
{
int x=find(e[i].x),y=find(e[i].y);
if (x!=y) f[x]=y,ans.x+=e[i].s,ans.y+=e[i].t;
}
return ans;
}
data solve(point a,point b)
{
for (int i=1;i<=m;i++) e[i].w=-e[i].s*(b.y-a.y)-e[i].t*(a.x-b.x);
point c=kruskal();
if (c==a||c==b||(LL)(c.x-a.x)*(b.y-a.y)-(LL)(c.y-a.y)*(b.x-a.x)<=0) return std::min((data){(LL)a.x*a.y,a.x,a.y},(data){(LL)b.x*b.y,b.x,b.y});
return std::min(solve(a,c),solve(c,b));
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
{
scanf("%d%d%d%d",&e[i].x,&e[i].y,&e[i].s,&e[i].t);
e[i].x++;e[i].y++;
}
for (int i=1;i<=m;i++) e[i].w=e[i].s;
point st=kruskal();
for (int i=1;i<=m;i++) e[i].w=e[i].t;
point ed=kruskal();
data ans=solve(st,ed);
printf("%d %d\n",ans.x,ans.y);
return 0;
}