凸包
二维凸包
定义:
凸多边形:凸多边形是指所有内角大小都在\([0,\pi]\)范围内的简单多边形.
凸包:在平面上能包含所有给定点的最小凸多边形叫做凸包
其定义为:对于给定集合\(X\),所有包含\(X\)的凸集的交集\(S\)被称为\(X\)的凸包.
如图,连线构成的凸多边形就是凸包:
\(Andrew\)算法求凸包
性质:
该算法时间复杂度为\(O(nlogn)\)其中\(n\)为待求凸包点集的大小,同时复杂度的瓶颈也在于对所有点坐标的双关键字排序.
过程
首先把所有点以横坐标为第一关键字,纵坐标为第二关键字排序.
显然排序后最小元素和最大元素一定在凸包上.而且因为是凸多边形,我们如果从一个点出发逆时针走,其轨迹总是左拐的,一旦右拐,说明这一段不在凸包上.因此我们可以用一个单调栈维护上下凸壳.因为从左向右看,上下凸壳所旋转的方向不同,为了让单调栈起作用,我们首先升序枚举求出下凸壳,然后降序求出上凸壳.
求凸壳是,一旦发现即将进展的点和栈顶的两个点行进的方向向右旋转,即叉积小于0,则弹出栈顶,回到上一步,继续检测,直到叉积大于等于0或者站内仅剩一个元素为止.
code:
// stk[] 是整型,存的是下标
// p[] 存储向量或点
tp = 0; // 初始化栈
std::sort(p + 1, p + 1 + n); // 对点进行排序
stk[++tp] = 1;
// 栈内添加第一个元素,且不更新 used,使得 1 在最后封闭凸包时也对单调栈更新
for (int i = 2; i <= n; ++i) {
while (tp >= 2 // 下一行 * 操作符被重载为叉积
&& (p[stk[tp]] - p[stk[tp - 1]]) * (p[i] - p[stk[tp]]) <= 0)
used[stk[tp--]] = 0;
used[i] = 1; // used 表示在凸壳上
stk[++tp] = i;
}
int tmp = tp; // tmp 表示下凸壳大小
for (int i = n - 1; i > 0; --i)
if (!used[i]) {
// ↓求上凸壳时不影响下凸壳
while (tp > tmp && (p[stk[tp]] - p[stk[tp - 1]]) * (p[i] - p[stk[tp]]) <= 0)
used[stk[tp--]] = 0;
used[i] = 1;
stk[++tp] = i;
}
for (int i = 1; i <= tp; ++i) // 复制到新数组中去
h[i] = p[stk[i]];
int ans = tp - 1;
,不多解释,直接上代码
#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
struct node{
double x,y;
}in[MAXN];
bool cmp(node a,node b)
{
if(a.x==b.x)return a.y<b.y;
return a.x<b.x;
}
stack<int>a,b;
double mult(double x1,double y1,double x2,double y2)
{
return x1*y2-x2*y1;
}
double SQ(double x1,double y1,double x2,double y2)
{
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
int ansline[MAXN*2+10];
int main()
{
//freopen("in.txt","r",stdin);
int n;
cin>>n;
for(int i=1;i<=n;++i)
{
scanf("%lf%lf",&in[i].x,&in[i].y);
}
sort(in+1,in+1+n,cmp);
stack<int>a;
a.push(1);
for(int i=2;i<=n;++i)
{
if(a.size()==1)
{
a.push(i);
continue;
}
while(a.size()>1){
int S1=a.top();
a.pop();
int S2=a.top();
a.push(S1);
if(mult(in[S1].x-in[S2].x,in[S1].y-in[S2].y,in[i].x-in[S1].x,in[i].y-in[S1].y)<0)a.pop();
else break;
}
a.push(i);
}
int l=a.size();
for(int i=1;i<=l;++i)
{
ansline[i]=a.top();a.pop();
}
a.push(n);
for(int i=n-1;i>=1;--i)
{
if(a.size()==1)
{
a.push(i);
continue;
}
while(a.size()>1){
int S1=a.top();
a.pop();
int S2=a.top();
a.push(S1);
if(mult(in[S1].x-in[S2].x,in[S1].y-in[S2].y,in[i].x-in[S1].x,in[i].y-in[S1].y)<0)a.pop();
else break;
}
a.push(i);
}
int g=a.size();
for(int i=1;i<=g;++i)
{
ansline[i+l]=a.top();a.pop();
}
double ans=0;
for(int i=1;i<l+g;++i)
{
ans+=SQ(in[ansline[i]].x,in[ansline[i]].y,in[ansline[i+1]].x,in[ansline[i+1]].y);
//cout<<ansline[i]<<" ";
}
//ans+=SQ(in[ansline[l+g]].x,in[ansline[l+g]].y,in[ansline[1]].x,in[ansline[1]].y);
//cout<<SQ(in[ansline[l+g]].x,in[ansline[l+g]].y,in[ansline[1]].x,in[ansline[1]].y)<<endl;
printf("%.2lf\n",ans);
return 0;
}
再上一道变式题(来源:【JZOJ NOIP2019模拟2019.9.4】A )
题解:
自己写的代码:(有点丑,别介意)
#include<bits/stdc++.h>
#define MAXN 1000005
using namespace std;
int n,q;
struct node
{
long long a,b;
};
long long f(node a,long long x)
{
return a.a*x+a.b;
}
bool cmp(node a,node b)
{
if(a.a==b.a)return a.b<b.b;
return a.a<b.a;
}
node u[MAXN],d[MAXN],stu[MAXN],Std[MAXN];
int cntu,cntd;
long long mult(long long x1,long long y1,long long x2,long long y2)
{
return x1*y2-x2*y1;
}
void tubao(node U[],int n,node st[],int &cnt)
{
sort(U+1,U+n+1,cmp);
for(int i=1;i<=n;++i)
{
while((cnt>=1&&st[cnt].a==U[i].a)|| ((st[cnt-1].b-st[cnt].b)*(U[i].a-st[cnt-1].a)-(st[cnt-1].b-U[i].b)*(st[cnt].a-st[cnt-1].a)>=0)&&cnt>=2)
cnt--;
//依旧是叉乘,然后可以把ax+b看成是点(a,b),这样就转化为正常求凸包了
st[++cnt]=U[i];
}
}
int sum;
long long divide(node S[],int cnt,int x)
{
int l=1,r=cnt;
while(l<=r)
{//++sum;
int mid=(l+r)>>1;
if(f(S[mid],x)>f(S[mid+1],x))
r=mid-1;
else {l=mid+1;}
}
return f(S[l],x)*x;
}
long long read()
{
long long x=0,f=1;char ch=0;
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return f*x;
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
cin>>n>>q;
for(int i=1;i<=n;++i)
{
node in;
in.a=read(),in.b=read();
u[i]=in;
in.a=-in.a,in.b=-in.b;
d[i]=in;
}
tubao(u,n,stu,cntu);
tubao(d,n,Std,cntd);
while(q--)
{
long long x;
scanf("%lld",&x);
if(x>0)printf("%lld",divide(stu,cntu,x));
else printf("%lld",-divide(Std,cntd,x));
putchar('\n');
}
//cout<<sum<<endl;
return 0;
}