测试地址:Art Gallery
题目大意:给定一个多边形,求多边形内有多大的区域使得站在这些区域中的任何一点都可以看到整个多边形的内部。
做法:本题需要用到半平面交。
要能从一个点看到一条边,该点就必须在这条边连成的直线上靠内的那一侧,并且没有其他边遮挡。
n
n
条边就相当于有个上述的限制条件,于是就相当于求半平面的交。
用排序增量法来解决这一问题。首先将所有半平面用向量表示,向量的逆时针(左侧)就代表这个半平面。然后将这些向量进行极角排序,用atan2函数可以较快地解决,注意在这一步中如果遇到平行的向量,仅取最靠内的一个向量,否则后面求交点会出错。
接下来我们维护一个双端队列,每次新增一个半平面,并分别检查队列的头和尾。如果队列头或尾的两个半平面的交点(即向量延长成的直线的交点)不在当前半平面内,那么就把队头或尾的那个半平面删去,因为它已经被更加严格的限制条件取代了。
要注意的是,上述算法执行完后,我们仍要进行一次删除操作,因为队头的半平面可能反过来取代队尾的半平面,而队尾的半平面也同样可能取代队头的半平面。
这样一来,我们依次求出剩余队列中相邻两个半平面的交点,这些交点顺次连成一个凸多边形,这就是我们要求的交了。算法复杂度为
O(nlogn)
O
(
n
log
n
)
,瓶颈在于排序部分,而后续的处理是
O(n)
O
(
n
)
的。
要注意,一开始题目所给出的顶点顺序可能是顺时针也可能是逆时针,可以用叉积算出多边形的面积,并通过这个面积的符号来判断顶点的顺序。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
const double eps=1.0e-12;
int T,n,q[1510];
struct Point
{
double x,y;
}p[1510];
struct Line
{
Point a,b;
double ang;
}l[1510];
Point operator - (Point a,Point b)
{
Point s={a.x-b.x,a.y-b.y};
return s;
}
double multi(Point a,Point b)
{
return a.x*b.y-b.x*a.y;
}
Point inter(Line a,Line b)
{
double a1=multi(b.b-a.a,b.a-a.a),a2=multi(b.a-a.b,b.b-a.b);
Point s={(a2*a.a.x+a1*a.b.x)/(a2+a1),(a2*a.a.y+a1*a.b.y)/(a2+a1)};
return s;
}
bool JudgeOut(Point a,Line b)
{
return multi(a-b.a,b.b-b.a)>0;
}
bool cmp(Line a,Line b)
{
if (fabs(a.ang-b.ang)<eps) return multi(a.b-b.a,b.b-b.a)<0;
else return a.ang<b.ang;
}
double solve()
{
sort(l+1,l+n+1,cmp);
int tmp=0,top,bottom;
for(int i=1;i<=n;i++)
if (fabs(l[i-1].ang-l[i].ang)>eps) l[++tmp]=l[i];
top=bottom=q[1]=1;
for(int i=2;i<=tmp;i++)
{
while(bottom<top&&JudgeOut(inter(l[q[top-1]],l[q[top]]),l[i])) top--;
while(bottom<top&&JudgeOut(inter(l[q[bottom]],l[q[bottom+1]]),l[i])) bottom++;
q[++top]=i;
}
while(bottom<top&&JudgeOut(inter(l[q[top-1]],l[q[top]]),l[q[bottom]])) top--;
while(bottom<top&&JudgeOut(inter(l[q[bottom]],l[q[bottom+1]]),l[q[top]])) bottom++;
if (top-bottom<=1) return 0.00;
else
{
for(int i=bottom;i<top;i++)
p[i-bottom+1]=inter(l[q[i]],l[q[i+1]]);
p[top-bottom+1]=inter(l[q[top]],l[q[bottom]]);
double ans=0.0;
for(int i=1;i<=top-bottom;i++)
ans+=multi(p[i],p[i+1]);
ans+=multi(p[top-bottom+1],p[1]);
return ans/2.0;
}
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lf%lf",&p[i].x,&p[i].y);
double S=0.0;
for(int i=1;i<n;i++)
{
l[i].a=p[i],l[i].b=p[i+1];
S+=multi(p[i],p[i+1]);
}
l[n].a=p[n],l[n].b=p[1];
S+=multi(p[n],p[1]);
if (S<0.0) for(int i=1;i<=n;i++) swap(l[i].a,l[i].b);
for(int i=1;i<=n;i++) l[i].ang=atan2(l[i].b.y-l[i].a.y,l[i].b.x-l[i].a.x);
printf("%.2f\n",solve());
}
return 0;
}