【BZOJ】2395: [Balkan 2011]Timeismoney-最小乘积生成树

传送门:bzoj2395


题解

一篇很好的题解

主要采用了数形结合思想,将总代价看做二维平面上的一个点, ∑ c \sum c c相当于 x x x坐标, ∑ t \sum t t相当于 y y y坐标。每次都是在一个下凸壳上找最小方案:具体来说,首先找到 ∑ c \sum c c最小的点 A A A(按 c c c做MST),以及 ∑ t \sum t t最小的点 B B B(按 t t t做MST),如果有更优点 C C C,必然在有向线段 A B AB AB右侧,贪心找到离 A B AB AB最远的 C C C,即 c r o s s ( B − A , C − A ) cross(B-A,C-A) cross(BA,CA)最小:

忽略 c r o s s ( B − A , C − A ) cross(B-A,C-A) cross(BA,CA)中的常数项,相当于使得 ( B x − A x ) C y + ( A y − B y ) C x (Bx-Ax)Cy+(Ay-By)Cx (BxAx)Cy+(AyBy)Cx最小。
于是边权设为 ( B x − A x ) t i + ( A y − B y ) c i (Bx-Ax)t_i+(Ay-By)c_i (BxAx)ti+(AyBy)ci做一遍MST求出 C C C,如果 C C C A B AB AB右侧,那么迭代下去找 A C , C B AC,CB AC,CB右侧的点,直到右侧没有点。这个过程中的所有点都可能更新答案。

复杂度 O ( 凸 包 上 点 × ( m log ⁡ m + n α ( n ) ) ) O(凸包上点\times (m\log m+n\alpha(n))) O(×(mlogm+nα(n)))


代码

点下标是从0开始的,改了好久才发现。。。

#include<bits/stdc++.h>
using namespace std;
const int N=202,M=1e4+10,inf=1e9;
typedef long long ll;

int n,m,f[N];

struct P{
   int x,y;
   P(int x_=0,int y_=0):x(x_),y(y_){};
   inline P operator -(const P&ky){return P(x-ky.x,y-ky.y);}
   inline ll operator ^(const P&ky){return (ll)x*ky.y-(ll)y*ky.x;}
}ans,mn,mx;

inline void chk(P &a,P b){
    ll re=(ll)a.x*a.y-(ll)b.x*b.y;
	if(re>0 || (re==0 && b.x<a.x)) a=b; 
}

struct pr{
	int u,v,c,t,w;
	bool operator<(const pr&ky)const{return w<ky.w;}
}le[M];

inline int F(int x){return (x==f[x])?x:(f[x]=F(f[x]));}
inline P MST()
{
	int i,x,y,k=0;P re=P(0,0);
	sort(le+1,le+m+1);
	for(i=0;i<n;++i) f[i]=i;
	for(i=1;k+1<n && i<=m;++i){
		x=F(le[i].u);y=F(le[i].v);
		if(x==y) continue;
		f[x]=y;k++;
		re.x+=le[i].c;re.y+=le[i].t;
	}
	if(k+1<n) re=P(inf,inf);//
	return re;
}

void sol(P a,P b)
{
	int i;P c;
	for(i=1;i<=m;++i)
	 le[i].w=(b.x-a.x)*le[i].t+(a.y-b.y)*le[i].c;
	c=MST();
	if(((b-a)^(c-a))>=0) return;
	chk(ans,c);sol(a,c);sol(c,b);
}

int main(){
	int i,j,x,y;ans=P(inf,inf);
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;++i) 
	  scanf("%d%d%d%d",&le[i].u,&le[i].v,&le[i].c,&le[i].t);
	for(i=1;i<=m;++i) le[i].w=le[i].c;mn=MST();
	for(i=1;i<=m;++i) le[i].w=le[i].t;mx=MST();
	chk(ans,mn);chk(ans,mx);sol(mn,mx);
	printf("%d %d",ans.x,ans.y);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值