Inconvenient Pairs(线段树/二分)

题目
题意:给定由n个横街道和m个纵街道组成的地图(其中边界0和1000000默认都是街道);给定k个在街道上的行人(显然这些行人所在的横或纵坐标至少有一个在街道上)。求有多少对人,他们之间的最短距离不是曼哈顿距离(当然,两个人之间只能通过街道走路,不能穿墙)。
思路:在草稿纸上画一画,可以较容易的得出结论。对于所在横、纵坐标都处于街道上的人(如下图小黄),他去任何其他人的最短距离都是最短距离。
在这里插入图片描述

对于只有横或纵坐标处于街道上的人(如下图小黄),不失一般性,不妨设该人所在横坐标在街道上,设离他最近的左纵街道为L,右纵街道为R,那么小黄和L左边的人,以及右边的人的最短距离都是曼哈顿距离。小黄和(L,R)中的距离不是最短距离(如下图浅绿),但需要排除(L,R)中和小黄同个街道的人(如下图深绿)。因此,我们可以按顺序添加行人。计算浅绿人头,可以用区间查询,分别维护横和纵方向的线段树;计算深绿人头,可以用二分,维护每条街道的有序集合(代码用的是set)。
在这里插入图片描述

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
const int maxn = 200010;
const int maxm = 300010;
const int mx = 1000001;
#define lson (rt << 1)
#define rson (rt << 1 | 1)

unordered_set<int> X, Y;
unordered_map<int, set<int> > mp[2];
int arr[2][maxn];
int n, m, k;
int x[maxm], y[maxm];
int len[2], p[2];
int sum[2][(mx + 5) << 2];

void pushup(int rt, int pos) {
	sum[pos][rt] = sum[pos][lson] + sum[pos][rson];
}

ll query(int rt, int l, int r, int a, int b, int pos) {
	if (a <= l && r <= b) {
		return sum[pos][rt];
	}
	int m = (l + r) / 2;
	ll res = 0;
	if (a <= m) res += query(lson, l, m, a, b, pos);
	if (m < b) res += query(rson, m + 1, r, a, b, pos);
	return res;
}

void update(int rt, int l, int r, int x, int val, int pos) {
	if (l == r) {
		sum[pos][rt] += val;
		return;
	}
	int m = (l + r) / 2;
	if (x <= m) update(lson, l, m, x, val, pos);
	else update(rson, m + 1, r, x, val, pos);
	pushup(rt, pos);
}
// 查找最大的小于val的下标 
int binaryL(int pos, int val) { 
	int l = 0, r = len[pos] - 1;
	while (l < r) {
		int m = (l + r + 1) / 2;
		if (arr[pos][m] < val) {
			l = m;
		} else {
			r = m - 1;
		}
	}
	return l;
}
int main() {
	int t;
	scanf("%d", &t);
	memset(sum, 0, sizeof(sum));// build 线段树可以复用 
	while (t--) {
		scanf("%d%d%d", &n, &m, &k);
		X.clear();mp[0].clear();
		// 下标都加一处理 
		for (int i = 0; i < n; ++i) {
			scanf("%d", &arr[0][i]);
			X.insert(++arr[0][i]);
			mp[0][arr[0][i]] = set<int>();
		}
		Y.clear();mp[1].clear();
		for (int i = 0; i < m; ++i) {
			scanf("%d", &arr[1][i]);
			Y.insert(++arr[1][i]);
			mp[1][arr[1][i]] = set<int>();
		}
//		build(1, 1, mx)
		ll ans = 0;
		len[0] = n;
		len[1] = m;
		mp[0].clear();mp[1].clear();
		for (int i = 0; i < k; ++i) {
			scanf("%d%d", &x[i], &y[i]);
			p[0] = ++x[i];
			p[1] = ++y[i];
			// 超级小黄,到哪都是曼哈顿 
			if (X.find(p[0]) != X.end() && Y.find(p[1]) != Y.end()) {
//				printf("(%d, %d) +%d\n", p[0]-1, p[1]-1, 0);
				continue;
			}
			int pos = 0;
			if (Y.find(p[1]) != Y.end()) {
				pos = 1;
			}
			int pos2 = 1 - pos;
			int l = binaryL(pos2, p[pos2]);
			int r = l + 1;
			int L = arr[pos2][l] + 1, R = arr[pos2][r] - 1;
//			int r = binaryR(1 - pos, p[1-pos]);

			mp[pos][p[pos]].insert(p[pos2]);// 更新街道集合,先插入,再做查询 
			ll tmp = query(1, 1, mx, L, R, pos2);//计算浅绿人头+深绿人头 
			ll tmp2 = distance(mp[pos][p[pos]].lower_bound(L),// 计算深绿人头 
					           mp[pos][p[pos]].upper_bound(R)) - 1;// 减去小黄自己 
//			printf("(%d, %d) +%d-%d %s:[%d, %d] - %s:mp[%d]\n", p[0] - 1, p[1] - 1, tmp, tmp2, 
//					pos2 == 0 ? "line" : "col", L - 1, R - 1, pos2 == 0 ? "col" : "line", p[pos] - 1);
			ans += tmp - tmp2;

			// 更新线段树 
			update(1, 1, mx, p[1], 1, 1);
			update(1, 1, mx, p[0], 1, 0);
			
		}
		
		printf("%lld\n", ans);
		
		// 清空线段树 
		for (int i = 0; i < k; ++i) {
			p[0] = x[i];
			p[1] = y[i];
			if (X.find(p[0]) != X.end() && Y.find(p[1]) != Y.end()) {
				continue;
			}
			update(1, 1, mx, p[1], -1, 1);
			update(1, 1, mx, p[0], -1, 0);
		}
	}
}
/*
4
5 4 9
0 1 2 6 1000000
0 4 8 1000000
4 4
2 5
2 2
6 3
1000000 1
3 8
5 8
8 8
6 8

*/
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值