2017 ACM-ICPC World Finals D Money for Nothing

题目

在一个平面直角坐标系上有 n n n个矩形的左下端点和 m m m个矩形的右上端点。

找到一个左下端点和一个右上端点,使得形成的矩形面积最大。

n , m ≤ 5 ∗ 1 0 5 n,m\le 5*10^5 n,m5105


似曾相识。

首先将一些显然不会最优的点去掉,那么就会得到两个点的序列,都是从左上到右下排布。

考虑对于一个右上端点 a a a,假如有左下端点 p p p比左下端点 q q q优:

( x a − x p ) ( y a − y p ) ≥ ( x a − x q ) ( y a − y q ) (x_a-x_p)(y_a-y_p)\ge (x_a-x_q)(y_a-y_q) (xaxp)(yayp)(xaxq)(yayq)

整理得 ( x p x q − y p y q ) − x a ( y p − y q ) − y a ( x p − x q ) > 0 (x_px_q-y_py_q)-x_a(y_p-y_q)-y_a(x_p-x_q)>0 (xpxqypyq)xa(ypyq)ya(xpxq)>0

如果 p < q p<q p<q,那么 y p − y q > 0 , x p − x q < 0 y_p-y_q>0,x_p-x_q<0 ypyq>0,xpxq<0,所以当 x a x_a xa减小或 y a y_a ya增大时,这条式子仍然成立。此时如果有 b < a b<a b<a,那么对于 b b b来说,选择 p p p比选择 q q q优。

如果 p > q p>q p>q,类似地考虑。

于是就可以发现决策单调性。

直接按顺序DP是不行的,因为如果要找到决策点,那么要从上一个位置的决策点找到序列末尾。

所以可以分治地做:设 ( L , R , l , r ) (L,R,l,r) (L,R,l,r)表示处理右上端点 [ L , R ] [L,R] [L,R]区间,左下端点 [ l , r ] [l,r] [l,r]区间。每次取 [ L , R ] [L,R] [L,R]中点 m i d mid mid,找到它的最优决策点 p p p,然后分成两个子问题 ( L , m i d − 1 , l , p ) (L,mid-1,l,p) (L,mid1,l,p) ( m i d + 1 , R , p , r ) (mid+1,R,p,r) (mid+1,R,p,r)

可能会出的锅是会出现不合法的情况,就是它们实际上的位置关系并不满足右上左下的偏序关系。

麻烦的做法是:求出对于每个右上端点的合法的左下端点的区间。求出 p p p之后,将序列分成三个部分:区间包含 p p p的,区间在 p p p左边的,区间在 p p p右边的,然后继续做。实现比较复杂,反正我没有成功。时间复杂度应该是 O ( n lg ⁡ n ) O(n\lg n) O(nlgn)的。

简单的做法是:不合法的情况只需要考虑,右上变左下,左下变右上的情况。发现如果出现这种情况,那么两个序列各自分成两段,两边变成子问题继续做下去是没有问题的。因为如果出现了如此的 m i d mid mid i i i i i i贡献不要求最大),那么 ( m i d , R ] (mid,R] (mid,R] [ l , i ) [l,i) [l,i)不会互相影响。时间复杂度也是 O ( n lg ⁡ n ) O(n\lg n) O(nlgn).


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define N 500010
#define ll long long
#define INF 2000000000
int n,m;
struct DOT{
	int x,y;
} a[N],b[N];
bool cmpd(DOT a,DOT b){return a.x<b.x || a.x==b.x && a.y<b.y;}
void init(DOT a[],int &n,int f){
	if (f==-1)
		for (int i=1;i<=n;++i)
			a[i].x*=-1,a[i].y*=-1;
	sort(a+1,a+n+1,cmpd);
	int t=0;
	int y=INF;
	for (int i=1;i<=n;++i)
		if (i==1 || a[i].y<y)
			a[++t]=a[i],y=a[i].y;
	n=t;
	if (f==-1){
		for (int i=1;i<=n;++i)
			a[i].x*=-1,a[i].y*=-1;
		reverse(a+1,a+n+1);
	}
}
int L[N],R[N],pl[N],pr[N];
ll ans;
ll calc(int i,int p){
	return (ll)(b[i].x-a[p].x)*(b[i].y-a[p].y);
}
void divide(int bl,int br,int al,int ar){
	if (bl>br || al>ar) return;
	int mid=bl+br>>1;
	int p=-1;
	for (int i=al;i<=ar;++i){
		if (b[mid].x<=a[i].x && b[mid].y<=a[i].y){
			divide(bl,mid-1,al,i-1);
			divide(mid+1,br,i+1,ar);
			return;
		}
		if (p==-1 || calc(mid,p)<calc(mid,i))
			p=i;
	}
	if (p!=-1)
		ans=max(ans,calc(mid,p));
	divide(bl,mid-1,al,p);
	divide(mid+1,br,p,ar);
}
int main(){
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;++i)
		scanf("%d%d",&a[i].x,&a[i].y);
	for (int i=1;i<=m;++i)
		scanf("%d%d",&b[i].x,&b[i].y);
	init(a,n,1);
	init(b,m,-1);
	for (int i=1;i<n;++i)
		assert(a[i].x<a[i+1].x && a[i].y>a[i+1].y);
	for (int i=1;i<m;++i)
		assert(b[i].x<b[i+1].x && b[i].y>b[i+1].y);
	divide(1,m,1,n);
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值