.
题意:给出一个简单多边形,问有多少个点能在原点被看见。
题解:set
先定义next(x)为x的下一个点,prev(x)为x的上一个点,multi(p0,p1,p2)为叉积。假设点的顺序是顺时针,如图所示。
很明显,只需要从右往左的边,也就是multi(O,x,next(x))>0的边,就可以挡住所有的点;换言之,只有这些边上的点有可能被看见。还是简单证明一下吧:对于上面的边(细边)它们已经被下面的边完全挡住了,不用考虑;对于一条在下面的从左往右的边,要从first走到它的起点——也就是左边的点,一定会先越过它;而从它的终点——也就是右边的点,要走到last,也一定会越过它。这两部分一个从这条边上面越过,另一部分从下面越过,一定可以覆盖这条边。
所以我们只需要把所有点按极角排序,从first逆时针扫到last,每个点作为以这个点为起点的边(一定是从右往左的边)插入一个可以插入元素、维护最小值、删除元素的数据结构中(线段树、堆、平衡树都可以实现。我用了set),删除以这个点为终点的边(假如在这个数据结构里的话),每次看看当前点是不是最靠下的(没有被挡住的)。
注意点也是可以挡住视线的,所以最后要处理一下极角相同,也就是与原点在同一条直线上的点,除了最近的,把剩下的都标为看不见就好了。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
long long n,a[200010];
struct pnt
{
long long x,y;
}p[200010],o;
bool ans[200010],rev=0;
long long next(long long x)
{
return x==n?1:x+1;
}
long long prev(long long x)
{
return x==1?n:x-1;
}
long long multi(pnt p0,pnt p1,pnt p2)
{
long long x1=p1.x-p0.x,y1=p1.y-p0.y,x2=p2.x-p0.x,y2=p2.y-p0.y;
return x1*y2-x2*y1;
}
long long cmp(long long x,long long y)
{
return multi(o,p[x],p[y])==0?p[x].x<p[y].x:multi(o,p[x],p[y])>0;
}
void pre()
{
p[0]=p[n];
p[n+1]=p[1];
long long st=1;
for(long long i=2;i<=n;i++)
{
if(multi(o,p[st],p[i])<0||multi(o,p[st],p[i])==0&&p[i].x<p[st].x)
st=i;
}
if(multi(p[st],p[next(st)],p[prev(st)])>0)
reverse(p,p+2+n),rev=1,st=n-st+1;
for(long long i=1;i<=n;i++)
{
a[i]=i;
}
sort(a+1,a+n+1,cmp);
}
struct hhh
{
long long x;
hhh(long long _x)
{
x=_x;
}
bool operator<(hhh y)const
{
if(x==y.x)
return 0;
return multi(o,p[x],p[y.x])>=0&&multi(p[x],p[next(x)],p[y.x])<=0||multi(o,p[x],p[y.x])<0&&multi(p[y.x],p[next(y.x)],p[x])>0;
}
};
void out()
{
if(rev)
reverse(ans+1,ans+1+n);
vector<int>hh;
for(long long i=1;i<=n;i++)
if(ans[i])
hh.push_back(i);
printf("%lld\n",hh.size());
for(vector<int>::iterator it=hh.begin();it!=hh.end();it++)
printf("%lld ",*it);
}
set<hhh>s;
bool in[200010];
int main()
{
o={0,0};
scanf("%lld",&n);
for(long long i=1;i<=n;i++)
scanf("%lld%lld",&p[i].x,&p[i].y);
pre();
for(long long i=1;i<=n;i++)
{
if(in[prev(a[i])])
{
if(s.begin()->x==prev(a[i]))
ans[a[i]]=1;
in[prev(a[i])]=0;
set<hhh>::iterator it=s.lower_bound(hhh(prev(a[i])));
// it--;
// printf("%lld %lld\n",it->x,prev(a[i]));
s.erase(it);
}
if(multi(o,p[a[i]],p[next(a[i])])>0)//只有这些边是有用的
{
s.insert(hhh(a[i]));
in[a[i]]=1;
long long uu=s.begin()->x;
if(multi(p[uu],p[next(uu)],p[a[i]])==0)
ans[a[i]]=1;
}
else if(multi(p[s.begin()->x],p[next(s.begin()->x)],p[a[i]])==0)
ans[a[i]]=1;/*
printf("%lld\n",a[i]);
for(long long i=1;i<=n;i++)
printf("%lld ",in[i]);
puts("");
print();*/
}
ans[a[n]]=1;
for(long long i=1,j=1;i<=n;i=j)
{
j++;
while(j<=n&&multi(o,p[a[i]],p[a[j]])==0)
ans[a[j++]]=0;
}
out();
}