[题解]P4166 [SCOI2007] 最大土地面积
解法\(1\) - \(O(n^2)\)
我们运用调整法,可以证明这个四边形的\(4\)个顶点一定都在凸包的顶点上,具体来说:
\(\textbf{Proof:}\)
首先我们知道,凸包内,到某条直线距离最大的点一定包括\(1\)个顶点。
接下来我们考虑一个凸包内的四边形:
我们过它的对角线做一条直线\(JL\),然后我们就根据上面的结论知道,到\(JL\)距离最大的点一定是凸包的某一个顶点。因此经过调整,把\(M\)和\(K\)都调整到顶点的位置,距离增大了,自然面积也增大。同理,一直调整到所有四边形顶点都在凸包的顶点上,我们就证明了“不是所有顶点都在凸包顶点上的四边形,面积一定不是最大”,即证明了“面积最大的四边形一定所有顶点都在凸包的顶点上”。\(\color{#f0f0f0}\textbf{Q.E.D.}\)
接下来我们就可以枚举凸包的顶点来解决了,但最暴力的方法\(O(n^4)\)会超时,想想怎么优化。
受旋转卡壳的启发,我们想到枚举一条对角线,然后找\(2\)个离这条对角线最远的点,分别位于对角线的两侧。和旋转卡壳相同地,这一步也是可以用双指针的思想优化的。
#1
#include<bits/stdc++.h>
#define nxtnode(x) (x%top+1)
#define N 2010
using namespace std;
struct vec{
double x,y;
vec operator+(vec b){return {x+b.x,y+b.y};}
vec operator-(vec b){return {x-b.x,y-b.y};}
}a[N];
bool cmp(vec a,vec b){return a.x==b.x?a.y<b.y:a.x<b.x;}
double cross(vec a,vec b){return a.x*b.y-a.y*b.x;}
int n,st[N],top;
bool vis[N];
double ans;
void convex_hull(){
sort(a+1,a+1+n,cmp);
memset(vis,0,sizeof vis);
st[top=1]=1;
for(int i=2;i<=n;i++){
while(top>1&&cross(a[st[top]]-a[st[top-1]],a[i]-a[st[top]])>=0){
vis[st[top--]]=0;
}
vis[i]=1,st[++top]=i;
}
int tmp=top;
for(int i=n-1;i>=1;i--){
if(vis[i]) continue;
while(top!=tmp&&cross(a[st[top]]-a[st[top-1]],a[i]-a[st[top]])>=0){
vis[st[top--]]=0;
}
vis[i]=1,st[++top]=i;
}
top--;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i].x>>a[i].y;
convex_hull();
int p1,p2;
for(int i=1;i<top;i++){
p1=p2=i;
for(int j=i+1;j<=top;j++){
while(cross(a[st[j]]-a[st[i]],a[st[nxtnode(p1)]]-a[st[i]])>=cross(a[st[j]]-a[st[i]],a[st[p1]]-a[st[i]])){
p1=nxtnode(p1);
}
while(cross(a[st[j]]-a[st[i]],a[st[nxtnode(p2)]]-a[st[i]])<=cross(a[st[j]]-a[st[i]],a[st[p2]]-a[st[i]])){
p2=nxtnode(p2);
}
ans=max(ans,cross(a[st[j]]-a[st[i]],a[st[p1]]-a[st[i]])-cross(a[st[j]]-a[st[i]],a[st[p2]]-a[st[i]]));
}
}
cout<<fixed<<setprecision(3)<<fabs(ans)/2<<"\n";
return 0;
}
解法\(2\) - \(O(n\log n)\)
进一步分析,我们可以发现,这个面积最大的四边形的对角线一定是对踵点的连线。
\(\textbf{Proof:}\)
我们假设对角线不全是对踵点的连线:
连接\(CG\),则一定能找到到\(CG\)上面离它更远的点,使得面积更大。
此时\(J\)和\(E\)都离\(CG\)最远,无法继续调整,它们是对踵点,
所以我们可以想到一个\(O(n)\)的做法来完成这一过程。
仅需枚举一个顶点\(U\),然后找到它的对踵点\(V\),再找到\(UV\)两侧,离\(UV\)最远的两点\(A,B\)。每次更新答案即可。
根据\(U\)找\(V\)可以用双指针的思想来优化,和旋转卡壳枚举点的做法相似。相应地,我们发现线段\(UV\)是按一个方向旋转的,所以\(A,B\)的枚举也可以双指针。
注意第\(1\)个步骤如果出现共线的情况,两个点都需要考虑,不过注意顺序(\(50,51\)行),否则不能保证\(UV\)按一个方向旋转,导致出现错误,会被4 -15 -4 4 13 15 4 -4 -13
卡掉(虽然交上去会A)。
这一步骤是\(O(n)\)的,加上凸包的\(O(n\log n)\),总时间复杂度\(O(n\log n)\)。
#2
#include<bits/stdc++.h>
#define nxtnode(x) (x%top+1)
#define N 50010
using namespace std;
struct vec{
double x,y;
vec operator+(vec b){return {x+b.x,y+b.y};}
vec operator-(vec b){return {x-b.x,y-b.y};}
int squalen(){return x*x+y*y;}
}a[N];
bool cmp(vec a,vec b){return a.x==b.x?a.y<b.y:a.x<b.x;}
double cross(vec a,vec b){return a.x*b.y-a.y*b.x;}
int n,st[N],top;
double ans;
void convex_hull(){
sort(a+1,a+1+n,cmp);
st[top=1]=1;
for(int i=2;i<=n;i++){
while(top>1&&cross(a[st[top]]-a[st[top-1]],a[i]-a[st[top]])>=0)
top--;
st[++top]=i;
}
int tmp=top;
for(int i=n-1;i>=1;i--){
while(top!=tmp&&cross(a[st[top]]-a[st[top-1]],a[i]-a[st[top]])>=0)
top--;
st[++top]=i;
}
top--;
}
void modify(int i,int j,int &k,int &l){//k从i开始,l从j开始
double tk,tl;
while(cross(a[st[j]]-a[st[i]],a[st[nxtnode(k)]]-a[st[i]])>=(tk=cross(a[st[j]]-a[st[i]],a[st[k]]-a[st[i]])))
k=nxtnode(k);
while(cross(a[st[j]]-a[st[i]],a[st[nxtnode(l)]]-a[st[i]])<=(tl=cross(a[st[j]]-a[st[i]],a[st[l]]-a[st[i]])))
l=nxtnode(l);
ans=max(ans,(tk-tl)/2);
}
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i].x>>a[i].y;
convex_hull();//顺时针
int j=2,k=1,l=2;
for(int i=1;i<=top;i++){
double t;
while((t=cross(a[st[nxtnode(i)]]-a[st[i]],a[st[nxtnode(j)]]-a[st[j]]))<0){
if(l==j) l=nxtnode(l);
j=nxtnode(j);
}
modify(i,j,k,l);
if(t==0) modify(i,nxtnode(j),k,l);//两句不能反
}
cout<<fixed<<setprecision(3)<<ans<<"\n";
return 0;
}