补给站

Description

WYF 为了保证他自己能够吃到足够多的牛排,来补充自己的脑力,所以他建了两个补给站,坐标分别为 ( a x , a y ) , ( b x , b y ) (ax,ay),(bx,by) (ax,ay),(bx,by)。他有 n n n 个休息地点,第 i i i 个休息地点的坐标是 ( x i , y i ) (xi,yi) (xi,yi)。每个补给站都有一个补给半径,当一个休息地点在以一个补给站为圆心,该补给站的补给半径为半径的圆中时(包括在圆周上),那个休息地点就会获得补给。现在有 m m m 个询问,每个询问会给出第一个补给站的补给半径 r 1 r1 r1 和第二个补给站的补给半径 r 2 r2 r2 ,WYF想知道有多少个休息地点会得到补给。

Input

输入的第一行包含两个整数, n n n m m m
第二行包含4个整数, a x , a y , b x , b y ax , ay , bx , by ax,ay,bx,by
第3至第 n + 2 n + 2 n+2 行,每行包含两个整数 x , y x , y x,y
n + 3 n + 3 n+3 n + m + 2 n + m + 2 n+m+2 行包含两个整数 r 1 , r 2 r1 , r2 r1,r2

Output

输出的第1至 m m m 行包含 1 1 1 个整数,表示其所对应的询问的答案

Sample Input

4 2
-1 0 2 0
0 0
1 1
2 2
0 2
3 1
1 1

Sample Output

3
1

Data

对于 100% 的数据
n ≤ 2 ∗ 1 0 5 , m ≤ 1 0 5 , a x , a y , b x , b y , x , y ∈ [ − 100000 , 100000 ] , r 1 , r 2 ∈ [ 0 , 300000 ] n \leq 2 * 10^5,\\ m \leq 10^5,\\ ax,ay,bx,by,x,y\in[-100000,100000],\\ r1,r2\in[0,300000] n2105,m105,ax,ay,bx,by,x,y[100000,100000],r1,r2[0,300000]

Associate

比较困难的一道题。30%还是比较好打的,但是考试的时候觉得自己想出来正解了(实际上差了一点点),然后就写的正解,没去管30%
然后获得了10分的好成绩(真是个奇迹因为我写的全都是错的)

Solution

其实和我在考试的时候想的差不多,但是整道题包括输入和查询都需要离线(如果您会可持久化线段树就请您关闭此网页谢谢)
可是我只写了输入的离线,查询是在线的。一层O(n)再套上memset(还有!memset是约等于O(n)的!),直接 n 2 n^2 n2 炸了。跑的比暴力还慢(还有个 log ⁡ n \log n logn 的线段树)

这一道题,我们首先考虑暴力。暴力明显是 O ( n 2 ) O(n^2) O(n2) 的,显然对于 1 0 5 10^5 105 是过不了的。我们考虑优化。
因为每一次对于每一个节点都进行重复的搜索非常的浪费时间,我们先考虑把每一个休息点到两个补给站的距离都先统计出来,分别记为 d i s a [ ] disa[] disa[] d i s b [ ] disb[] disb[] 然后我们对于 d i s a [ ] disa[] disa[] d i s b [ ] disb[] disb[] 都排序。然后,在输入 r 1 r1 r1 r 2 r2 r2 的时候,我们只需要在 d i s a [ ] disa[] disa[] d i s b [ ] disb[] disb[] 中查找比 r 1 r1 r1 r 2 r2 r2 小的元素个数就行了。因为整个数列都是有序的,我们可以考虑二分查找。由于本人实在是懒得写二分了,我们考虑 upper_bound。把两个数组中小于两个半径的个数,分别记为 p o s 1 pos1 pos1 p o s 2 pos2 pos2
然后我们就发现了一个问题:可能会存在同时比 r 1 r1 r1 r 2 r2 r2 小的元素,按照这种算法,就算了两次,那么怎么去重呢?
这里面,一共有两个维度,分别是比 r 1 r1 r1 小和比 r 2 r2 r2 小。因为这两个是等价的,我们考虑把随便取出一个维度,比如说 r 1 r1 r1。这个时候,我们在 d i s a [ ] disa[] disa[] 扫一边所有比 r 1 r1 r1 小的元素。不管三七二十一,我们把这些元素的 d i s b [ ] disb[] disb[] 通通扔到线段树里面去。当我们把所有的都扔完了之后,我们考虑线段树的区间查询操作,直接查询,大功告成。

然后你获得了20分的好成绩。
为什么呢?因为你每次都需要初始化整颗线段树,在 O ( n ) O(n) O(n) 里面套上 memset,获得了 O ( n 2 ) O(n^2) O(n2) 的超高复杂度。加上线段树,你跑的比暴力还慢。

那怎么办呢?
我们考虑把查询也离线掉,剩下的思路大同小异。
输入所有的查询,然后把他们按照一个关键字排序。就比如说我按照距离第二个补给站的距离排序。
这样我们得到了一个已经排好序的查询序列。这个时候,我们再开始循环。
为什么要这样做?
因为,这样每一次的查询半径越来越大,那么在上一个查询范围内的休息点必定也在当前查询范围内,就可以省去每次重新初始化整颗线段树和重复加入前面的节点的多余操作,大大提高了代码的效率。每一次记录一下上一次添加到哪一个休息点了,下一次从这里开始加入休息点到线段树里面就行了。剩下的操作和上面的就一样了。现在的这颗线段树,只会越来越大,而每一次查询都是 O ( log ⁡ n ) O(\log n) O(logn) 的,加上外面循环查询的 O ( n ) O(n) O(n) ,总时间复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn) ,并不会超时。

写的时候还有一些小细节,需要注意一下,在这里就不多罗嗦了
同时,如果你还不会线段树,可以采用树状数组,能达到一样的效果

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>

using namespace std;

typedef long long ll;

inline ll read() {
    ll x = 0 , f = 1; char ch = getchar();
    for ( ; !isdigit(ch) ; ch = getchar()) if (ch == '-') f = -1;
    for ( ; isdigit(ch) ; ch = getchar()) x = x * 10 + ch - '0';
    return x * f;
}

const ll maxn = 4e5 + 10;

struct SegTree {
  ll LeftSon , RightSon;
  ll LazyTag;
}segTree[maxn << 2];

ll weight[maxn << 2];

inline void pushUp(ll root) {
  	weight[root] = weight[root << 1] + weight[root << 1 | 1];
}

inline void pushDown(ll root) {
	if (segTree[root].LazyTag) {
		weight[root << 1] += segTree[root].LazyTag * (segTree[root << 1].RightSon - segTree[root << 1].LeftSon + 1);
		weight[root << 1 | 1] += segTree[root].LazyTag * (segTree[root << 1 | 1].RightSon - segTree[root << 1 | 1].LeftSon + 1);
		
		segTree[root << 1].LazyTag += segTree[root].LazyTag;
		segTree[root << 1 | 1].LazyTag += segTree[root].LazyTag;
		segTree[root].LazyTag = 0;
	}
}

void build(ll root , ll _left , ll _right) {
	segTree[root].LeftSon = _left;
	segTree[root].RightSon = _right;
	
	if (_left == _right) {
		return ;
	}
	
	ll _mid = (_left + _right) >> 1;
	build(root << 1 , _left , _mid);
	build(root << 1 | 1 , _mid + 1 , _right);
	
	pushUp(root);
	
	return ;
}

ll query(ll root , ll _left , ll _right) {
	if (_left <= segTree[root].LeftSon && segTree[root].RightSon <= _right) {
		return weight[root];
	}
	
	pushDown(root);
	
	ll _mid = (segTree[root].LeftSon + segTree[root].RightSon) >> 1;
	ll ans = 0;
	if (_left <= _mid) {
		ans += query(root << 1 , _left , _right);
	}
	if (_mid < _right) {
		ans += query(root << 1 | 1 , _left , _right);
	}
	
	return ans;
}

void change(ll root , ll _left , ll _right , ll add) {
	if (_left <= segTree[root].LeftSon && segTree[root].RightSon <= _right) {
		weight[root] += (segTree[root].RightSon - segTree[root].LeftSon + 1) * add;
		segTree[root].LazyTag += add;
		return ;
	}
	
	pushDown(root);
	
	ll _mid = (segTree[root].LeftSon + segTree[root].RightSon) >> 1;
	if (_left <= _mid) {
		change(root << 1 , _left , _right , add);
	}
	if (_mid < _right) {
		change(root << 1 | 1 , _left  , _right , add);
	}
	
	pushUp(root);
	return ;
}

struct Node {
	ll x , y;
	double dis1 , dis2;
}node[maxn];

ll n , m; 

ll ax , ay , bx , by;

inline double getDis(ll a , ll b , ll c , ll d) {
	return sqrt((double) (a - c) * (double) (a - c) + (double) (b - d) * (double) (b - d));
}

double disa[maxn] , disb[maxn];

inline bool cmp1(Node a , Node b) {
	return a.dis1 < b.dis1;
}

inline bool cmp2(Node a , Node b) {
	return a.dis2 < b.dis2;
}

struct Quest {
	ll r1 , r2 , id , ans;
	
	bool operator < (const Quest &k) const {
		if (this->r2 == k.r2) {
			return this->r1 < k.r1;
		}
		return this->r2 < k.r2;
	}
}quest[maxn];

inline bool check(Quest a , Quest b) {
	return a.id < b.id;
}

int main() {
	n = read() , m = read();
	ax = read() , ay = read() , bx = read() , by = read();
	
	for (ll i = 1 ; i <= n ; i ++) {
		node[i].x = read() , node[i].y = read();
		node[i].dis1 = getDis(node[i].x , node[i].y , ax , ay);
		node[i].dis2 = getDis(node[i].x , node[i].y , bx , by);
	}
	
	sort(node + 1 , node + n + 1 , cmp1);
	for (ll i = 1 ; i <= n ; i ++) {
		disa[i] = node[i].dis1;
	}
	
	sort(node + 1 , node + n + 1 , cmp2);
	for (ll i = 1 ; i <= n ; i ++) {
		disb[i] =  node[i].dis2;
	}
	
	for (ll i = 1 ; i <= m ; i ++) {
		quest[i].r1 = read() , quest[i].r2 = read();
		quest[i].id = i;
	}
	
	sort(quest + 1 , quest + m + 1);
	
	build(1 , 0 , maxn - 1);
	
	ll lastPos = 1;
	
	for (ll i = 1 ; i <= m ; i ++) {
		ll pos1 = upper_bound(disa + 1 , disa + n + 1 , (double) quest[i].r1) - disa - 1;
		ll pos2 = upper_bound(disb + 1 , disb + n + 1 , (double) quest[i].r2) - disb - 1;
		ll ans = pos1 + pos2;
		
		for (ll j = lastPos ; j <= pos2 ; j ++) {
			ll now = (ll) node[j].dis1;
			if ((double) now < node[j].dis1) {
				now ++;
			}
			
			change(1 , now , now , 1);
		}
		
		lastPos = pos2 + 1;
		
		quest[i].ans = ans - query(1 , 0 , quest[i].r1);
	}
	
	sort(quest + 1 , quest + m + 1 , check);
	
	for (ll i = 1 ; i <= m ; i ++) {
		printf("%lld\n" , quest[i].ans);
	}
}
/*
4 2 
-1 0 2 0
0 0 
1 1
2 2 
0 2
3 1 
1 1
*/ 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值