bzoj2395 Timeismoney

传送门

最小乘积生成树板题。分别将 s u m c sum_c sumc s u m t sum_t sumt当做横坐标与纵坐标,于是对于任意的一个生成树都可以对应到坐标系中的一个点。我们只需要找出 x ∗ y x*y xy最小的那个点。步骤如下:
首先分别找到 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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值