This year, ACM/ICPC World finals will be held in a hall in form of a simple polygon. The coaches and spectators are seated along the edges of the polygon. We want to place a rotating scoreboard somewhere in the hall such that a spectator sitting anywhere on the boundary of the hall can view the scoreboard (i.e., his line of sight is not blocked by a wall). Note that if the line of sight of a spectator is tangent to the polygon boundary (either in a vertex or in an edge), he can still view the scoreboard. You may view spectator's seats as points along the boundary of the simple polygon, and consider the scoreboard as a point as well. Your program is given the corners of the hall (the vertices of the polygon), and must check if there is a location for the scoreboard (a point inside the polygon) such that the scoreboard can be viewed from any point on the edges of the polygon.
Input
The first number in the input line, T is the number of test cases. Each test case is specified on a single line of input in the form n x1 y1 x2 y2 ... xn yn where n (3 ≤ n ≤ 100) is the number of vertices in the polygon, and the pair of integers xi yi sequence specify the vertices of the polygon sorted in order.
Output
The output contains T lines, each corresponding to an input test case in that order. The output line contains either YES or NO depending on whether the scoreboard can be placed inside the hall conforming to the problem conditions.
Sample Input
2 4 0 0 0 1 1 1 1 0 8 0 0 0 2 1 2 1 1 2 1 2 2 3 2 3 0
Sample Output
YES NO
题意: 给出一个具有n个顶点的多边形,判断其内部是否存在一点使得该点处能够看到内部所有点。
分析: 在多边形内部能够看到所有点的区域称为多边形的核,这个区域就是各边的半平面交,因此转化为判断半平面交是否存在。求半平面交可以用S&I算法,时间复杂度O(nlogn),是一个不错的模板。
具体代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
const double eps = 1e-8;
const double inf = 1e20;
const double pi = acos(-1.0);
const int maxp = 1010;
//`Compares a double to zero`
int sgn(double x)
{
if(fabs(x) < eps)return 0;
if(x < 0)return -1;
else return 1;
}
struct Point
{
double x, y;
Point(){}
Point(double _x,double _y){x = _x, y = _y;}
void input(){scanf("%lf%lf",&x,&y);}
void output(){printf("%.2f %.2f\n",x,y);}
bool operator == (Point b)const{return sgn(x-b.x) == 0 && sgn(y-b.y) == 0;}
bool operator < (Point b)const{return sgn(x-b.x)== 0?sgn(y-b.y)<0:x<b.x;}
Point operator -(const Point &b)const{return Point(x-b.x,y-b.y);}
//叉积
double operator ^(const Point &b)const{return x*b.y - y*b.x;}
//点积
double operator *(const Point &b)const{return x*b.x + y*b.y;}
//返回长度
double len(){return hypot(x,y);/*库函数*/}
//返回长度的平方
double len2(){return x*x + y*y;}
//返回两点的距离
double distance(Point p){return hypot(x-p.x,y-p.y);}
Point operator +(const Point &b)const{return Point(x+b.x,y+b.y);}
Point operator *(const double &k)const{return Point(x*k,y*k);}
Point operator /(const double &k)const{return Point(x/k,y/k);}
//`计算pa 和 pb 的夹角`
//`就是求这个点看a,b 所成的夹角`
//`测试 LightOJ1203`
double rad(Point a,Point b)
{
Point p = *this;
return fabs(atan2( fabs((a-p)^(b-p)),(a-p)*(b-p) ));
}
//`化为长度为r的向量`
Point trunc(double r)
{
double l = len();
if(!sgn(l))return *this;
r /= l;
return Point(x*r,y*r);
}
//`逆时针旋转90度`
Point rotleft(){return Point(-y,x);}
//`顺时针旋转90度`
Point rotright(){return Point(y,-x);}
//`绕着p点逆时针旋转angle`
Point rotate(Point p,double angle)
{
Point v = (*this) - p;
double c = cos(angle), s = sin(angle);
return Point(p.x + v.x*c - v.y*s,p.y + v.x*s + v.y*c);
}
};
struct Line
{
Point s,e;
Line(){}
Line(Point _s,Point _e){s = _s, e = _e;}
bool operator ==(Line v){return (s == v.s)&&(e == v.e);}
//`根据一个点和倾斜角angle确定直线,0<=angle<pi`
Line(Point p,double angle)
{
s = p;
if(sgn(angle-pi/2) == 0){e = (s + Point(0,1));}
else{e = (s + Point(1,tan(angle)));}
}
//ax+by+c=0
Line(double a,double b,double c)
{
if(sgn(a) == 0) s = Point(0,-c/b), e = Point(1,-c/b);
else if(sgn(b) == 0) s = Point(-c/a,0), e = Point(-c/a,1);
else s = Point(0,-c/b), e = Point(1,(-c-a)/b);
}
void input()
{
s.input();
e.input();
}
void adjust(){if(e < s)swap(s,e);}
//求线段长度
double length(){return s.distance(e);}
//`返回直线倾斜角 0<=angle<pi`
double angle()
{
double k = atan2(e.y-s.y,e.x-s.x);
if(sgn(k) < 0)k += pi;
if(sgn(k-pi) == 0)k -= pi;
return k;
}
//`点和直线关系`
//`1 在左侧`
//`2 在右侧`
//`3 在直线上`
int relation(Point p)
{
int c = sgn((p-s)^(e-s));
if(c < 0)return 1;
else if(c > 0)return 2;
else return 3;
}
// 点在线段上的判断
bool pointonseg(Point p){return sgn((p-s)^(e-s)) == 0 && sgn((p-s)*(p-e)) <= 0;}
//`两向量平行(对应直线平行或重合)`
bool parallel(Line v){return sgn((e-s)^(v.e-v.s)) == 0;/*两向量叉积为0*/ }
Point crosspoint(Line v)
{
double a1 = (v.e-v.s)^(s-v.s);
double a2 = (v.e-v.s)^(e-v.s);
return Point((s.x*a2-e.x*a1)/(a2-a1),(s.y*a2-e.y*a1)/(a2-a1));
}
};
struct polygon{
int n;
Point p[maxp];
Line l[maxp];
void input(int _n){
n = _n;
for(int i = 0;i < n;i++)
p[i].input();
}
void add(Point q){
p[n++] = q;
}
void getline(){
for(int i = 0;i < n;i++){
l[i] = Line(p[i],p[(i+1)%n]);
}
}
struct cmp{
Point p;
cmp(const Point &p0){p = p0;}
bool operator()(const Point &aa,const Point &bb){
Point a = aa, b = bb;
int d = sgn((a-p)^(b-p));
if(d == 0){
return sgn(a.distance(p)-b.distance(p)) < 0;//极角相同,距离原点近的靠前
}
return d > 0;
}
};
//`进行极角排序`
//`首先需要找到最左下角的点`
//`需要重载号好Point的 < 操作符(min函数要用) `
void norm(){
Point mi = p[0];
for(int i = 1;i < n;i++)mi = min(mi,p[i]);
sort(p,p+n,cmp(mi));
}
//`得到凸包`
//`得到的凸包里面的点编号是0$\sim$n-1的`
//`两种凸包的方法`
//`注意如果有影响,要特判下所有点共点,或者共线的特殊情况`
//`测试 LightOJ1203 LightOJ1239`
void getconvex(polygon &convex){
sort(p,p+n);
convex.n = n;
for(int i = 0;i < min(n,2);i++){
convex.p[i] = p[i];
}
if(convex.n == 2 && (convex.p[0] == convex.p[1]))convex.n--;//特判
if(n <= 2)return;
int &top = convex.n;
top = 1;
for(int i = 2;i < n;i++){
while(top && sgn((convex.p[top]-p[i])^(convex.p[top-1]-p[i])) <= 0)
top--;
convex.p[++top] = p[i];
}
int temp = top;
convex.p[++top] = p[n-2];
for(int i = n-3;i >= 0;i--){
while(top != temp && sgn((convex.p[top]-p[i])^(convex.p[top-1]-p[i])) <= 0)
top--;
convex.p[++top] = p[i];
}
if(convex.n == 2 && (convex.p[0] == convex.p[1]))convex.n--;//特判
convex.norm();//`原来得到的是顺时针的点,排序后逆时针`
}
//`得到凸包的另外一种方法`
//`测试 LightOJ1203 LightOJ1239`
void Graham(polygon &convex){
norm();
int &top = convex.n;
top = 0;
if(n == 1){
top = 1;
convex.p[0] = p[0];
return;
}
if(n == 2){
top = 2;
convex.p[0] = p[0];
convex.p[1] = p[1];
if(convex.p[0] == convex.p[1])top--;
return;
}
convex.p[0] = p[0];
convex.p[1] = p[1];
top = 2;
for(int i = 2;i < n;i++){
while( top > 1 && sgn((convex.p[top-1]-convex.p[top-2])^(p[i]-convex.p[top-2])) <= 0 )
top--;
convex.p[top++] = p[i];
}
if(convex.n == 2 && (convex.p[0] == convex.p[1]))convex.n--;//特判
}
//`判断是不是凸的`
bool isconvex(){
bool s[3];
memset(s,false,sizeof(s));
for(int i = 0;i < n;i++){
int j = (i+1)%n;
int k = (j+1)%n;
s[sgn((p[j]-p[i])^(p[k]-p[i]))+1] = true;
if(s[0] && s[2])return false;
}
return true;
}
//`判断点和任意多边形的关系`
//` 3 点上`
//` 2 边上`
//` 1 内部`
//` 0 外部`
int relationpoint(Point q){
for(int i = 0;i < n;i++){
if(p[i] == q)return 3;
}
getline();
for(int i = 0;i < n;i++){
if(l[i].pointonseg(q))return 2;
}
int cnt = 0;
for(int i = 0;i < n;i++){
int j = (i+1)%n;
int k = sgn((q-p[j])^(p[i]-p[j]));
int u = sgn(p[i].y-q.y);
int v = sgn(p[j].y-q.y);
if(k > 0 && u < 0 && v >= 0)cnt++;
if(k < 0 && v < 0 && u >= 0)cnt--;
}
return cnt != 0;
}
//`得到周长`
//`测试 LightOJ1239`
double getcircumference(){
double sum = 0;
for(int i = 0;i < n;i++){
sum += p[i].distance(p[(i+1)%n]);
}
return sum;
}
//`得到面积`
double getarea(){
double sum = 0;
for(int i = 0;i < n;i++){
sum += (p[i]^p[(i+1)%n]);
}
return fabs(sum)/2;
}
//`得到方向`
//` 1 表示逆时针,0表示顺时针`
bool getdir(){
double sum = 0;
for(int i = 0;i < n;i++)
sum += (p[i]^p[(i+1)%n]);
if(sgn(sum) > 0)return 1;
return 0;
}
//`得到重心`
Point getbarycentre(){
Point ret(0,0);
double area = 0;
for(int i = 1;i < n-1;i++){
double tmp = (p[i]-p[0])^(p[i+1]-p[0]);
if(sgn(tmp) == 0)continue;
area += tmp;
ret.x += (p[0].x+p[i].x+p[i+1].x)/3*tmp;
ret.y += (p[0].y+p[i].y+p[i+1].y)/3*tmp;
}
if(sgn(area)) ret = ret/area;
return ret;
}
};
struct halfplane:public Line{
double angle;
halfplane(){}
//`表示向量s->e逆时针(左侧)的半平面`
halfplane(Point _s,Point _e){
s = _s;
e = _e;
}
halfplane(Line v){
s = v.s;
e = v.e;
}
void calcangle(){
angle = atan2(e.y-s.y,e.x-s.x);
}
bool operator <(const halfplane &b)const{
return angle < b.angle;
}
};
struct halfplanes
{
int n;//需要输入
halfplane hp[maxp];//需要输入,且封闭区域都在向量逆时针方向
Point p[maxp];
int que[maxp];
int st,ed;//队列的头尾指针,且下标从0开始,指向元素就是头和尾
void push(halfplane tmp){hp[n++] = tmp;}
//去重
void unique()
{
int m = 1;
for(int i = 1;i < n;i++)
{
if(sgn(hp[i].angle-hp[i-1].angle) != 0)
hp[m++] = hp[i];
//去除极角相同的情况下,位置在右边(沿向量方向)的边
else if(sgn( (hp[m-1].e-hp[m-1].s)^(hp[i].s-hp[m-1].s) ) > 0)
hp[m-1] = hp[i];
}
n = m;
}
bool halfplaneinsert()//判断半平面交是否存在
{
for(int i = 0;i < n;i++)hp[i].calcangle();
sort(hp,hp+n);//先对倾斜角排序
unique();
que[st=0] = 0;
que[ed=1] = 1;
p[1] = hp[0].crosspoint(hp[1]);
for(int i = 2;i < n;i++){
while(st<ed && sgn((hp[i].e-hp[i].s)^(p[ed]-hp[i].s))<0)ed--;
while(st<ed && sgn((hp[i].e-hp[i].s)^(p[st+1]-hp[i].s))<0)st++;
que[++ed] = i;
if(hp[i].parallel(hp[que[ed-1]]))return false;
p[ed]=hp[i].crosspoint(hp[que[ed-1]]);
}
while(st<ed && sgn((hp[que[st]].e-hp[que[st]].s)^(p[ed]-hp[que[st]].s))<0)ed--;
while(st<ed && sgn((hp[que[ed]].e-hp[que[ed]].s)^(p[st+1]-hp[que[ed]].s))<0)st++;
if(st+1>=ed)return false;//最后剩下小于三条直线,表明半平面交不存在
return true;
}
//`得到最后半平面交得到的凸多边形`
//`需要先调用halfplaneinsert() 且返回true`
void getconvex(polygon &con){
p[st] = hp[que[st]].crosspoint(hp[que[ed]]);
con.n = ed-st+1;
for(int j = st,i = 0;j <= ed;i++,j++)
con.p[i] = p[j];
}
};
signed main()
{
int T;
cin >> T;
while(T--)
{
int n;
cin >> n;
polygon a;
a.input(n);
halfplanes b;
b.n = 0;
for(int i = n-1; i >= 0; i--)
{
halfplane temp(a.p[i], a.p[(i-1+n)%n]);
b.push(temp);
}
if(b.halfplaneinsert())
puts("YES");
else
puts("NO");
}
return 0;
}