洛谷 P5540 - [BalkanOI2011] timeismoney | 最小乘积生成树(最小生成树)

洛谷题面传送门

大概是一个比较 trivial 的小 trick?学过了就不要忘了哦(

莫名奇妙地想到了 yyq 的”hot tea 不常有,做过了就不能再错过了“

首先看到这种二维问题我们可以很自然地想到将它们映射到一个二维平面上,即我们将 \(\sum\limits_{e\in E}a_e\) 看作横坐标 \(x\),将 \(\sum\limits_{e\in E}b_e\) 看作纵坐标 \(y\),那么我们所求即是全部生成树表示的点当中横纵坐标之积最大的点。显然这些点肯定都在所有点组成的下凸壳上,因此我们只用求出下凸壳上的所有点然后依次更新答案即可。

那么怎么求下凸壳上的点呢?我们考虑分治,考虑求出所有点当中横坐标最小的点 \(A\) 和纵坐标最小的点 \(B\)——这个可以通过将边权赋为 \(a_e\)\(b_e\) 分别求一遍最小生成树求出,那么我们考虑求出满足 \(C\)\(AB\) 左下方且 \(S_{\triangle ABC}\) 最大的点 \(C\)——由于 \(C\)\(AB\) 左下方,根据计算几何那一套理论,\(S_{\triangle ABC}\) 最大即意味着 \(\vec{BA}\times\vec{BC}\) 最大,而 \(\vec{BA}\times\vec{BC}=(x_A-x_B)(y_C-y_B)-(x_C-x_B)(y_A-y_B)\),将括号打开,与 \(C\) 无关的放一边可以得到 \(\vec{BA}\times\vec{BC}\) 最大又意味着 \((x_A-x_B)y_C-(y_A-y_B)x_C\) 最大,因此考虑将每条边边权赋为 \((x_B-x_A)b_e-(y_A-y_B)a_e\) 然后跑一遍 MST 即可求出点 \(C\),如果我们发现求出的点 \(C\)\(AB\) 右上方那直接 return 掉即可,否则继续递归处理 \((A,C)\)\((C,B)\)

据说用了个什么 QuickHull 的求凸包算法,凸壳上的点数最多是值域的 \(\dfrac{2}{3}\) 次方,因此复杂度就是 \((na)^{2/3}·n\log n\),但是显然证明就不是我的事了(

不知道能不能推广到三维.jpg

const int MAXN=200;
const int MAXM=1e4;
struct edge{int u,v,w;} e[MAXM+5];
int n,m,a[MAXM+5],b[MAXM+5],f[MAXN+5];
int find(int x){return (!f[x])?x:f[x]=find(f[x]);}
void merge(int x,int y){x=find(x);y=find(y);f[x]=y;}
int ord[MAXM+5];pii ans=mp(0x3f3f3f3f,0x3f3f3f3f);
bool cmp(int x,int y){return e[x].w<e[y].w;}
pii kruskal(){
	memset(f,0,sizeof(f));for(int i=1;i<=m;i++) ord[i]=i;
	sort(ord+1,ord+m+1,cmp);int suma=0,sumb=0;
	for(int i=1;i<=m;i++){
		if(find(e[ord[i]].u)==find(e[ord[i]].v)) continue;
		merge(e[ord[i]].u,e[ord[i]].v);
		suma+=a[ord[i]];sumb+=b[ord[i]];
	} if(1ll*suma*sumb<1ll*ans.fi*ans.se||(1ll*suma*sumb==1ll*ans.fi*ans.se&&suma<ans.fi))
		ans=mp(suma,sumb);
	return mp(suma,sumb);
}
void solve(pii x,pii y){
	for(int i=1;i<=m;i++) e[i].w=b[i]*(y.fi-x.fi)+a[i]*(x.se-y.se);pii z=kruskal();
	if(1ll*(x.fi-y.fi)*(x.se-z.se)-1ll*(x.se-y.se)*(x.fi-z.fi)>=0) return;
	solve(x,z);solve(z,y);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d%d%d",&e[i].u,&e[i].v,&a[i],&b[i]);
		++e[i].u;++e[i].v;
	} pii x,y;
	for(int i=1;i<=m;i++) e[i].w=a[i];x=kruskal();
	for(int i=1;i<=m;i++) e[i].w=b[i];y=kruskal();
	solve(x,y);printf("%d %d\n",ans.fi,ans.se);
	return 0;
}
posted @ 2021-07-27 21:34  tzc_wk  阅读(92)  评论(2)    收藏  举报