[FROM BZOJ][Balkan 2011]Timeismoney

#2395 [Balkan 2011]Timeismoney

传送门

SOL
最小乘积生成树的模板题
k = x ∗ y k=x*y k=xy,那么使 k k k越大,函数 y = k / x y=k/x y=k/x在第一象限的一支就要越靠近坐标轴
我们先将按 x x x排序和按 y y y排序得到的答案求出来,在坐标轴上就是最靠近 x x x轴和 y y y轴的点
在这里插入图片描述
显然这不一定是最优的情况
于是要尝试在这2个点上进行扩展
显然更优的决策一定是出现在AB的左侧
设我们已经找到了一个点C(这是个不确定的点
在这里插入图片描述
这时候我们可以发现S△ABC= AB X AC /2的 相反数
而S△ABC面积最大的时候,即为函数图像最靠近坐标轴的时候,所以要最小化 AB X AC
即最小化:
( B . x − A . x , B . y − A . y ) X ( C . x − A . x , C . y − A . y ) (B.x-A.x,B.y -A.y) X (C.x-A.x,C.y-A.y) (B.xA.x,B.yA.y)X(C.xA.x,C.yA.y)
化简之后,抛开常数我们还有:
C . y ∗ ( B . x − A . x ) − C . x ( B . y − A . y ) C.y*(B.x-A.x)-C.x(B.y-A.y) C.y(B.xA.x)C.x(B.yA.y)
所以我们可以利用这个性质维护答案,按 y [ i ] ∗ ( B . x − A . x ) − x [ i ] ∗ ( B . y − A . y ) y[i]*(B.x-A.x)-x[i]*(B.y-A.y) y[i](B.xA.x)x[i](B.yA.y)排序
处理完当前的A,B之后,我们就可以得到一个新的点C,再用A,C和C,B分别进行扩展,继续逼近坐标轴,直到 AB X AC=0,因为这时候C与A或者B重合,不能再扩展了

代码:

#include<bits/stdc++.h>
using namespace std;
#define re register
#define int long long
inline int rd(){
	int re data=0;static char ch=0;ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))data=(data<<1)+(data<<3)+(ch^48),ch=getchar();
	return data;
}
const int N=205,M=1e4+5;
struct node{int u,v,x,y,c;}e[M];
struct pt{int x,y;}a,b,ans;
inline bool cmp(const node&a,const node&b){return a.c<b.c;}
int n,m,fa[N],eg;
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline pt kruskal(pt re ret=(pt){0,0}){
	for(int re i=1;i<=n;++i)fa[i]=i;
	eg=0,sort(e+1,e+m+1,cmp);
	for(int re i=1;i<=m;++i){
		int re fu=find(e[i].u),fv=find(e[i].v);
		if(fu==fv)continue;
		fa[fu]=fv,++eg,ret.x+=e[i].x,ret.y+=e[i].y;
		if(eg==n-1)break;
	}
	int re sans=ans.x*ans.y,sret=ret.x*ret.y;
	if(sans>sret||(sans==sret&&ret.x<ans.x))ans=ret;
	return ret;
}
inline pt del(const pt&a,const pt&b){return (pt){a.x-b.x,a.y-b.y};}
inline int cross(const pt&a,const pt&b){return a.x*b.y-a.y*b.x;}
inline void work(const pt&a,const pt&b){
	for(int re i=1;i<=m;++i)e[i].c=e[i].y*(b.x-a.x)-e[i].x*(b.y-a.y);
	pt re c=kruskal();
	if(cross(del(b,a),del(c,a))==0)return;
	work(a,c),work(c,b);
}
signed main(){
	n=rd(),m=rd(),ans.x=(int)1e9,ans.y=(int)1e9;
	for(int re i=1;i<=m;++i)e[i]=(node){rd()+1,rd()+1,rd(),rd()};
	for(int re i=1;i<=m;++i)e[i].c=e[i].x;a=kruskal();
	for(int re i=1;i<=m;++i)e[i].c=e[i].y;b=kruskal();	
	work(a,b),printf("%lld %lld",ans.x,ans.y),exit(0);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值