最小乘积生成树

思想和分治凸包算法很像。
把生成树的\((\sum_x,\sum_y)\)投射到平面上,我们要找到\((x,y)\)使得\(x,y\)最小。
可以证明,最优的\((x,y)\)在凸包上。
证明可见2018国集论文。
考虑怎么求凸包,可以分治,保证当前处理的\((l,r)\)是凸包上的连续一段。
先求出凸包的最左/最右点,设为\(A,B\)这可以用两次mst,我们只需要对\(\sum_x,\sum_y\) mst即可。
考虑凸包上每个点向\(AB\)作垂线。
假设\(C\)\(AB\)的距离最大,则\(C\)一定在凸包上,可以递归\((A,C),(C,B)\)处理。
考虑如何求出\(C\),这相当于求出\(C\)使得三角形\(ABC\)的大小最大。
考虑叉积,我们要最小化\(AB\times AC\)
推出式子后发现这是\((x_B-x_A)y_C+(y_A-y_B)x_C-(x_B-x_A)y_A+(y_B-y_A)x_A\)
\(-(x_B-x_A)y_A+(y_B-y_A)x_A\)只要知道\(A,B\)后就能求出。
\((x_B-x_A)y_C+(y_A-y_B)x_C\),可以把每条边的权值设为\((x_B-x_A)b_i+(y_A-y_B)a_i\),求mst。
时间复杂度比较玄学

#include<bits/stdc++.h>
using namespace std;
#define N 20010
#define int long long
int n,m,f[N];
struct no{
	int x,y,w,a,b;
}a[N];
int operator<(no x,no y){
	return x.w<y.w;
}
struct nn{
	int x,y;
}va;
int operator^(nn x,nn y){
	return x.x*y.y-x.y*y.x;
}
nn operator+(nn x,nn y){
	return ((nn){x.x+y.x,x.y+y.y});
}
nn operator-(nn x,nn y){
	return ((nn){x.x-y.x,x.y-y.y});
}
int fd(int x){
	return !f[x]?x:f[x]=fd(f[x]);
}
nn mst(){
	sort(a+1,a+m+1);
	nn ans;
	ans.x=ans.y=0;
	for(int i=1;i<=n;i++)
		f[i]=0;
	for(int i=1;i<=m;i++){
		int xx=fd(a[i].x),yy=fd(a[i].y);
		if(xx!=yy){
			f[xx]=yy;
			ans.x+=a[i].a;
			ans.y+=a[i].b;
		}
	}
	if(ans.x*ans.y<va.x*va.y)
		va=ans;
	else if(ans.x*ans.y==va.x*va.y&&ans.x>va.x)
		va=ans;
	return ans;
}
void fz(nn A,nn B){
	for(int i=1;i<=m;i++)
		a[i].w=(B.x-A.x)*a[i].b+(A.y-B.y)*a[i].a;
	nn C=mst();
	if(((B-A)^(C-A))>=0)
		return;
	fz(A,C);
	fz(C,B);
}
signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%lld%lld%lld%lld",&a[i].x,&a[i].y,&a[i].a,&a[i].b);
		a[i].x++;
		a[i].y++;
	}
	va.x=va.y=1e9;
	for(int i=1;i<=m;i++)
		a[i].w=a[i].a;
	nn A=mst();
	for(int i=1;i<=m;i++)
		a[i].w=a[i].b;
	nn B=mst();
	fz(A,B);
	printf("%lld %lld",va.x,va.y);
}

HNOI2014 画框
考虑上面那题的算法。
我们要求一组匹配,使得\((\sum A)*(\sum B)\)最小。
考虑求出\((\sum A,\sum B)\)构成的下凸包。
求出凸包最左/最右点可以把\((i,j)\)的边权设为\(a_{i,j}\)然后最小权匹配。
求出\(C\)点事实上是最小化\((x_B-x_A)y_C+(y_A-y_B)x_C\)
把一条边的权值设为\((x_B-x_A)y_C+(y_A-y_B)x_C\)然后最小权匹配即可。
可以把边权取反后最大权匹配

#include<bits/stdc++.h>
using namespace std;
#define N 110
#define int long long
int n,w[N][N],x[N][N],y[N][N],t[N],va,vx[N],vy[N],a[N],b[N],sl;
struct nn{
	int x,y;
};
int operator^(nn x,nn y){
	return x.x*y.y-x.y*y.x;
}
nn operator+(nn x,nn y){
	return ((nn){x.x+y.x,x.y+y.y});
}
nn operator-(nn x,nn y){
	return ((nn){x.x-y.x,x.y-y.y});
}
int dfs(int x){
	vx[x]=1;
	for(int i=1;i<=n;i++)
		if(!vy[i]){
			if(a[x]+b[i]==w[x][i]){
				vy[i]=1;
				if(!t[i]||dfs(t[i])){
					t[i]=x;
					return 1;
				}
			}
			else sl=min(sl,a[x]+b[i]-w[x][i]);
		}
	return 0;
}
nn km(){
	memset(t,0,sizeof(t));
	for(int i=1;i<=n;i++){
		b[i]=a[i]=0;
		for(int j=1;j<=n;j++)
			a[i]=max(a[i],w[i][j]);
	}
	for(int i=1;i<=n;i++){
		while(1){
			memset(vx,0,sizeof(vx));
			memset(vy,0,sizeof(vy));
			sl=1e9;
			if(dfs(i))break; 
			for(int j=1;j<=n;j++){
				if(vx[j])
					a[j]-=sl;
				if(vy[j])
					b[j]+=sl;
			}
		}
	}
	nn ans;
	ans.x=ans.y=0;
	for(int i=1;i<=n;i++){
		ans.x+=x[t[i]][i];
		ans.y+=y[t[i]][i];
	}
	va=min(va,ans.x*ans.y);
	return ans;
}
void fz(nn A,nn B){
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			w[i][j]=-((A.y-B.y)*x[i][j]+(B.x-A.x)*y[i][j]);
	nn C=km();
	if(((B-A)^(C-A))>=0)
		return;
	fz(A,C);
	fz(C,B);
}
signed main(){
	int T;
	scanf("%lld",&T);
	while(T--){
		scanf("%lld",&n);
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				scanf("%lld",&x[i][j]);
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				scanf("%lld",&y[i][j]);
		va=1e18;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				w[i][j]=-x[i][j];
		nn A=km();
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				w[i][j]=-y[i][j];
		nn B=km();
		fz(A,B);
		printf("%lld\n",va);
	}
}
posted @ 2021-05-25 14:56  celerity1  阅读(93)  评论(0)    收藏  举报