题目
题意:给定由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
*/