2019.02.22【HNOI2011】【BZOJ2338】【洛谷P3217】数矩形(暴力)(旋转卡壳(?))

本文深入探讨了在点集上寻找最大矩形的算法。通过分析矩形对角线特性,提出了一种基于对角线排序和枚举的高效算法,并对比了旋转卡壳算法的正确性和局限性。

BZOJ传送门

洛谷传送门


解析:

考虑什么样的点可能组成矩形,边已经行不通了,限制不够。

矩形的对角线等长且共中点。

所以将所有 n 2 n^2 n2条对角线提出来自定义排个序就行了。

然后暴力枚举所有合法的对角线对,可以证明,这样没有多余的枚举且每个矩形都枚举了,复杂度为为 O ( 矩 形 个 数 ) = O ( n 2 n ) O(矩形个数)=O(n^2\sqrt n) O()=O(n2n ),而且这样的数据极难构造,证明可以见:https://blog.youkuaiyun.com/wang3312362136/article/details/83003126

而且现在所有的题解都是这个做法,但是为什么旋转卡壳是错的呢?

考虑按照极角对所有共中点且等长的排序后,显然尽量垂直的答案会更大,这就是旋转卡壳的依据。

所以旋转卡壳的正确性哪里出了问题?

本博客最后有我的一份旋转卡壳的代码,但是我还是不知道哪里错了,如果有读者发现了错误,请与我联系,感激。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define cs const

inline int getint(){
	re char c;
	re bool f=0;
	while(!isdigit(c=gc()))if(c=='-')f=1;re int num=c^48;
	while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
	return f?-num:num;
}

struct line{
	int x_1,x_2,y_1,y_2;
	inline ll len()cs{return (ll)(x_1-x_2)*(x_1-x_2)+(ll)(y_1-y_2)*(y_1-y_2);}
	friend bool operator<(cs line &a,cs line &b){
		if(a.x_1+a.x_2!=b.x_1+b.x_2)return a.x_1+a.x_2<b.x_1+b.x_2;
		if(a.y_1+a.y_2!=b.y_1+b.y_2)return a.y_1+a.y_2<b.y_1+b.y_2;
		return a.len()<b.len();
	}
	friend bool operator==(cs line &a,cs line &b){
		return a.x_1+a.x_2==b.x_1+b.x_2&&a.y_1+a.y_2==b.y_1+b.y_2&&a.len()==b.len();
	}
}l[1500*1500+5];

inline ll area(cs line &a,cs line &b){
	assert(a==b);
	re int x_1=b.x_1-a.x_1,y_1=b.y_1-a.y_1;
	re int x_2=b.x_2-a.x_1,y_2=b.y_2-a.y_1;
	return max((ll)x_1*y_2,(ll)y_1*x_2)-min((ll)x_1*y_2,(ll)y_1*x_2);
}

struct Point{int x,y;}p[1505];
int n,cnt;
ll ans;

signed main(){
	n=getint();
	for(int re i=1;i<=n;++i){
		p[i].x=getint(),p[i].y=getint();
	}
	for(int re i=1;i<=n;++i){
		for(int re j=1;j<i;++j){
			++cnt;
			l[cnt].x_1=p[i].x;
			l[cnt].y_1=p[i].y;
			l[cnt].x_2=p[j].x;
			l[cnt].y_2=p[j].y;
		}
	}
	sort(l+1,l+cnt+1);
	for(int re i=1,j;i<=cnt;i=j+1){
		for(j=i;j+1<=cnt&&l[i]==l[j+1];++j);
		for(int re k=i;k<=j;++k){
			for(int re t=i;t<k;++t)
			ans=max(ans,area(l[k],l[t]));
		}
	}
	cout<<ans<<"\n";
	return 0;
}

代码(旋转卡壳):

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define cs const

inline int getint(){
	re char c;
	re bool f=0;
	while(!isdigit(c=gc()))if(c=='-')f=1;re int num=c^48;
	while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
	return f?-num:num;
}

struct line{
	int x_1,x_2,y_1,y_2;
	inline ll len()cs{return (ll)(x_1-x_2)*(x_1-x_2)+(ll)(y_1-y_2)*(y_1-y_2);}
	friend bool operator<(cs line &a,cs line &b){
		if(a.x_1+a.x_2!=b.x_1+b.x_2)return a.x_1+a.x_2<b.x_1+b.x_2;
		if(a.y_1+a.y_2!=b.y_1+b.y_2)return a.y_1+a.y_2<b.y_1+b.y_2;
		if(a.len()!=b.len())return a.len()<b.len();
		return a.x_1-a.x_2<b.x_1-b.x_1;
	}
	friend bool operator==(cs line &a,cs line &b){
		return a.x_1+a.x_2==b.x_1+b.x_2&&a.y_1+a.y_2==b.y_1+b.y_2&&a.len()==b.len();
	}
}l[1500*1500+5];

inline ll area(cs line &a,cs line &b){
	assert(a==b);
	re int x_1=b.x_1-a.x_1,y_1=b.y_1-a.y_1;
	re int x_2=b.x_2-a.x_1,y_2=b.y_2-a.y_1;
	return max((ll)x_1*y_2,(ll)y_1*x_2)-min((ll)x_1*y_2,(ll)y_1*x_2);
}

struct Point{int x,y;}p[1505];
int n,cnt;
ll ans;

signed main(){
	n=getint();
	for(int re i=1;i<=n;++i){
		p[i].x=getint(),p[i].y=getint();
	}
	for(int re i=1;i<=n;++i){
		for(int re j=1;j<i;++j){
			++cnt;
			l[cnt].x_1=p[i].x;
			l[cnt].y_1=p[i].y;
			l[cnt].x_2=p[j].x;
			l[cnt].y_2=p[j].y;
			if(l[cnt].y_1>l[cnt].y_2){
				swap(l[cnt].y_1,l[cnt].y_2);
				swap(l[cnt].x_1,l[cnt].x_2);
			}
			else if(l[cnt].y_1==l[cnt].y_2){
				if(l[cnt].x_1<l[cnt].x_2)swap(l[cnt].x_1,l[cnt].x_2);
			}
		}
	}
	sort(l+1,l+cnt+1);
	for(int re i=1,j;i<=cnt;i=j+1){
		for(j=i;j+1<=cnt&&l[i]==l[j+1];++j);
		int t=i;
		for(int re k=i;k<=j;++k){
			while(t+1<=j&&area(l[k],l[t])<=area(l[k],l[t+1]))++t;
			ans=max(ans,area(l[k],l[t]));
			if(t+1<=j)ans=max(ans,area(l[k],l[t+1]));
			if(t-1>=i)ans=max(ans,area(l[k],l[t-1]));
		}
	}
	cout<<ans<<"\n";
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值