Problem
给定一个山脉,把山脉的下部看作xOy平面直角坐标系中y=0的直线,用N个来顶点表示山脉。如图:
Lwins_Y uki有Q个操作,操作有两种:对某个点用魔法(即删点)或询问某个点对空视野的张角(可以超过180°)。譬如上图第3个点的张角是:
Input
第一行包含两个正整数N;Q,分别表示刚开始的时候山的顶点个数以及Lwins_Y uki 的行动个数。
接下来N 行,包含N 对数xi; yi,表示山的顶点的坐标(xi; yi)。
接下来Q 行,每行的格式有如下两种可能:
• 0 k,表示当下时刻Lwins_Y uki 的魔法释放在了一开始的从左往右数的第k 个顶点上。
• 1 k,表示Lwins_Y uki 想立即知道住在第k 个顶点上的生物的对空视野的张角。
输入数据保证Lwins_Y uki 的行动和询问合法,即不会询问或把魔法释放在一个已经消失的点上。
Output
你应该按照询问的顺序,每行回答一次LwinsY uki 的询问,张角用弧度制,保留6 位小数。
Hint
• 对于20% 的数据,N;Q <=1000。
• 对于另外55% 的数据,N;Q<= 30000。
• 对于100% 的数据,N;Q <= 100000。
注意,Lwins_Y uki 不会询问在两侧的顶点的对空视野的张角,但是它有可能使用魔法抹去它们。
Solution
20points:三角函数+(暴力 or 凸壳)
刚看这题我就懵逼了,然后马上意识到——这题我不可能得分,于是就没管这题。。。
先思考一下怎么求张角。
你可以推一推看一看夹角公式,然后学习一下c++的acos()、atan()、atan2()这些函数来解决这个问题。
当然,我们有一种更简单的做法。
先让我介绍一下c++的atan2()这个函数。假设某个角
θ
θ
的三角函数
tanθ=yx
t
a
n
θ
=
y
x
,那么atan2(x,y)就会帮你求出
θ
θ
这个角的弧度。而根据tan的定义,我们可以把atan2(x,y)理解为原点至点(x,y)的方位角,即与 x 轴的夹角。
那么,如何用atan2()来求夹角呢?我们可以举个栗子:
如上图,在平面直角坐标系xOy中,已知a、b的坐标,我们想求∠aOb。那么,我们可以用atan2()先求出∠bOx,再求出∠aOx,两者相减即可。
解决了张角的求法问题,又有一个问题接踵而至——对于每个询问点O,如何确定其a、b两点?
以b为例,我们可以画画图发现,我们要求的点b其实就是在O左边的所有点中,满足构成的直线Ob的斜率最小的一个点;点a恰恰相反。这样,我们就可以
O(nq)
O
(
n
q
)
暴力求出每个询问点的a、b两点。
当然,我们也可以求个上凸壳。
但是,囿于他会无耻地删点,所以:
时间复杂度:
O(nq)
O
(
n
q
)
。
Code
我并没有打啦╮(╯▽╰)╭
100points:CDQ分治
我们发现他会删点,用数据结构不好维护,所以考虑CDQ分治。
如果单纯地将操作进行分治,那么时间复杂度不好把握。因为我们的操作是删点,如果没有删点,就一直会有n个点,然后所有询问都要花费巨额时间,这样甚至会退化到比暴力还慢的
O(nqlog2q)
O
(
n
q
l
o
g
2
q
)
。
那么,我们可以将删点转化为加点。如果有些点没有被删过,就把它在最后删掉;然后将操作数组反转过来。
打到一半,我又发现一个问题:询问点不能插入到那个凸壳里,所以我们对于每个询问点,都要暴力扫一遍栈,找到询问点在凸壳中的上一个点。
不过,囿于数据是随机的某种原因,这样也过了,而且还很快:
其实这个可以二分。
以求点p的张角的左边为例,我们先将所有x坐标小于p的点做个上凸壳,然后加入一个栈中。我们可以发现:栈中有一半的点满足向量p-sta[i-1]在直线sta[i]-sta[i-1]左边,这样我们看的时候,sta[i-1]这个点会被sta[i]遮住;而有另一半的点满足向量p-sta[i-1]在直线sta[i]-sta[i-1]右边。具象地说,有一半的点不能被看见,有一半的点能被看见。这显然满足二分性质,所以我们二分那个中间点。
然后,求左右手向就可以用叉积。
时间复杂度:
O((n+q)∗(log2(n+q))2)
O
(
(
n
+
q
)
∗
(
l
o
g
2
(
n
+
q
)
)
2
)
。
尽管时间复杂度分析起来是比上面的方法快的,然而这种方法的代码竟然比上面的方法还慢:
Code
囿于此题代码过于高级,所以我会作下一些批注。
#include <cstdio>
#include <cmath>
#include <algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef double db;
const int N=1e5+1,Q=N<<1;
const db pi=acos(-1);//c++的三角函数是弧度制,而cos(π)=-1
int i,n,q,type[Q],query[Q];
bool del[N];
db left[Q];//Solution中所述的∠bOx
db right[Q];//Solution中所述的∠aOx
struct DOT//定义一个点或一个向量
{
db x,y;
DOT(db _x=0,db _y=0){x=_x,y=_y;}
}a[N];
DOT operator-(DOT a,DOT b){return DOT(a.x-b.x,a.y-b.y);}
inline bool cmpx(const DOT &x, const DOT &y) {return x.x < y.x;}
void scan()
{
scanf("%d%d", &n, &q);
fo(i,1,n)scanf("%lf%lf", &a[i].x, &a[i].y);
sort(a + 1, a + 1 + n, cmpx);
fo(i,1,q)
{
scanf("%d%d", &type[i], &query[i]);
del[query[i]]|=!type[i];
}
fo(i,1,n) if (!del[i]) query[++q] = i;//将没有被删的点在最后删掉
fo(i,1,q/2)
{
swap(type[i], type[q - i + 1]);
swap(query[i], query[q - i + 1]);
}//将删点转为加点
fo(i,1,q)left[i] = 1.5 * pi, right[i] = -0.5 * pi;//弧度制的1.5*pi即为270°,-0.5*pi即为-90°
}
int num[N],top;
DOT poi[N],sta[N];
inline bool cmp(int x, int y) {return a[query[x]].x < a[query[y]].x;}
inline db cro(DOT a,DOT b){return a.x*b.y-a.y*b.x;}//叉积
inline db cross(DOT o,DOT a,DOT b){return cro(a-o,b-o);}
void work(int pn,int qn)
{
if(!pn||!qn)return;
sort(poi+1,poi+pn+1,cmpx);
sort(num+1,num+qn+1,cmp);
top=0;int i,j=1;
fo(i,1,qn)//顺序做一遍
{
DOT p=a[query[num[i]]];
for (; j <= pn && poi[j].x < p.x; j++)
{
while(top>1)
if (cross(sta[top-1], sta[top], poi[j]) > 0)top--;
else break;//维护上凸壳
sta[++top] = poi[j];
}
if(!top)continue;
int l=1,r=top,mid;
while(l<r)
{
mid=l+r+1>>1;
if(cross(sta[mid-1],sta[mid],p)>0)
r=mid-1;
else l=mid;
}
db t = atan2(sta[l].y - p.y, sta[l].x - p.x);//计算Solution中所述的∠bOx
if (t < 0) t += 2 * pi;//atan2()的返回值是-pi~pi
left[num[i]] = min(left[num[i]], t);//bOx要尽量小
}
top=0;j=pn;
fd(i,qn,1)//逆序做一遍
{
DOT p=a[query[num[i]]];
for (; j >= 1 && poi[j].x > p.x; j--)
{
while(top>1)
if (cross(sta[top-1], sta[top], poi[j]) < 0)top--;
else break;
sta[++top] = poi[j];
}
if(!top)continue;
int l=1,r=top,mid;
while(l<r)
{
mid=l+r+1>>1;
if(cross(sta[mid-1],sta[mid],p)<0)
r=mid-1;
else l=mid;
}
db t = atan2(sta[l].y - p.y, sta[l].x - p.x);//计算Solution中所述的∠aOx
right[num[i]] = max(right[num[i]], t);//∠aOx要尽量大
}
}
void CDQpartition(int l,int r)
{
if(l==r)return;
int mid=l+r>>1;
int pn=0;
fo(i,l,mid)if(!type[i])poi[++pn]=a[query[i]];//记录左边的加点
int qn=0;
fo(i,i,r )if( type[i])num[++qn]=i;//记录右边的询问
work(pn,qn);
if(pn&&pn<mid-l+1)CDQpartition(l ,mid);
if(qn&&qn<r-mid )CDQpartition(mid+1,r );
}
void print()
{
fd(i,q,1)
if(type[i])
{
db t=left[i]-right[i];
if (t < 0) t += 2 * pi;
if (t > 2 * pi) t -= 2 * pi;
printf("%.6f\n",t);
}
}
int main()
{
scan();
CDQpartition(1,q);
print();
}