codeforces gym101630 Archery Tournament 线段树+set

46 篇文章 0 订阅
42 篇文章 0 订阅

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 (y1y2)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 xy 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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值