http://codeforces.com/gym/101630
题目大意:有 n n n个事件依次发生,每个事件以 ( o p , x , y ) (op,x,y) (op,x,y)的形式给出,若 o p = 1 op=1 op=1,代表以 ( x , y ) (x,y) (x,y)为圆心建立一个半径为 y y y的圆形靶子,数据保证所有的靶子都不相交,但是可能会相切;若 o p = 2 op=2 op=2,代表此时在 ( x , y ) (x,y) (x,y)处进行一次射击,如果此次射击在某个靶子的内部(边界不算),则输出这个靶子建立对应的事件编号(其实就是问它是第几次事件),否则输出 − 1 -1 −1;保证 − 1 0 9 < = x , y < = 1 0 9 -10^9<=x,y<=10^9 −109<=x,y<=109。
思路:首先要知道在任意一条垂直线上,最多切到了
O
(
l
o
g
1
0
9
)
O(log10^9)
O(log109)个靶子,证明如下:
如图由勾股定理易得:
(
y
1
−
y
2
)
2
+
y
2
2
=
(
y
1
+
y
2
)
2
(y_1-y_2)^2+y_2^2=(y_1+y_2)^2
(y1−y2)2+y22=(y1+y2)2,解得:
y
1
=
4
y
2
y_1=4y_2
y1=4y2。也就是说对于一次射击
(
x
,
y
)
(x,y)
(x,y),我们只要能快速的找到与
x
x
x相切的靶子就可以了,不难想到用线段树来做。但是横坐标的范围很大,需要进行离散化,靶子的横坐标有两个:
x
−
y
x-y
x−y和
x
+
y
x+y
x+y,射击的横坐标就是
x
x
x,离散化之后我们建立一个线段树,每个结点用
s
e
t
set
set维护该区间内的靶子的编号即可。时间复杂度为
O
(
n
l
o
g
n
l
o
g
1
0
9
)
O(nlognlog10^9)
O(nlognlog109),具体实现细节见代码。
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=4e5+5;
struct node
{
int op;
ll l,r;
}a[maxn>>1];//n个事件
struct Tree
{
int l,r;
set<int> id;
}tree[maxn<<2];//线段树
ll b[maxn];//横坐标离散化
int n,tot;
void build(int i,int l,int r)
{
tree[i].l=l,tree[i].r=r;
if(l==r)
return ;
int mid=l+r>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
}
void update(int i,int l,int r,int idx)
{
if(tree[i].l==l&&tree[i].r==r)//目标区间
{
tree[i].id.insert(idx);//插入该靶子的编号idx
return ;
}
int mid=tree[i].l+tree[i].r>>1;
if(r<=mid)
update(i<<1,l,r,idx);
else if(l>mid)
update(i<<1|1,l,r,idx);
else
update(i<<1,l,mid,idx),
update(i<<1|1,mid+1,r,idx);
}
int query(int i,int pos,int idx)
{
set<int> ::iterator it=tree[i].id.begin(),ed=tree[i].id.end();
ll x,y;
while(it!=ed)//暴力判断该次射击在哪个靶子内部
{
x=a[*it].l,y=a[*it].r;//靶子圆心坐标
if(((a[idx].l-x)*(a[idx].l-x)+(a[idx].r-y)*(a[idx].r-y))<y*y)//射击点在靶子内部
return *it;
++it;
}
if(tree[i].l==tree[i].r)//到了叶子节点还没有返回说明 该次射击未击中靶子
return -1;
int mid=tree[i].l+tree[i].r>>1;
if(pos<=mid)
return query(i<<1,pos,idx);
else
return query(i<<1|1,pos,idx);
}
void del(int i,int l,int r,int idx)
{
if(tree[i].l==l&&tree[i].r==r)
{
tree[i].id.erase(idx);
return ;
}
int mid=tree[i].l+tree[i].r>>1;
if(r<=mid)
del(i<<1,l,r,idx);
else if(l>mid)
del(i<<1|1,l,r,idx);
else
del(i<<1,l,mid,idx),
del(i<<1|1,mid+1,r,idx);
}
int main()
{
scanf("%d",&n);
int op;
for(int i=1;i<=n;i++)
{
scanf("%d %lld %lld",&a[i].op,&a[i].l,&a[i].r);
if(a[i].op==1)//建立靶子
b[++tot]=a[i].l-a[i].r,b[++tot]=a[i].l+a[i].r;
else //射击
b[++tot]=a[i].l;
}
sort(b+1,b+1+tot);//排序去重进行离散化
tot=unique(b+1,b+1+tot)-b-1;
build(1,1,tot);//建树
int l,r,pos,ans;
for(int i=1;i<=n;i++)
{
if(a[i].op==1)//建立靶子
{
l=lower_bound(b+1,b+1+tot,a[i].l-a[i].r)-b;//得到该靶子所在的区间[l,r]
r=lower_bound(b+1,b+1+tot,a[i].l+a[i].r)-b;
update(1,l,r,i);//在维护该区间的线段树结点的set中插入编号i
}
else //射击
{
pos=lower_bound(b+1,b+1+tot,a[i].l)-b;//得到该次射击离散化后的横坐标
ans=query(1,pos,i);//查询靶子编号
printf("%d\n",ans);
if(ans!=-1)//若靶子存在 进行删除操作
{
l=lower_bound(b+1,b+1+tot,a[ans].l-a[ans].r)-b;
r=lower_bound(b+1,b+1+tot,a[ans].l+a[ans].r)-b;
del(1,l,r,ans);
}
}
}
return 0;
}