[题解]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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效