AcWing119 袭击
目录
- AcWing119 袭击
- 题目描述
- 背景
- 输入
- 输出
- 数据范围
- 题解
- 解法
- 优化
- 打赏
AcWing119 袭击
题目描述
背景
特工进入据点突袭发电站,已知所有发电站的位置和所有特工的降落位置,求任意特工距离任意核电站的最短距离
输入
- 第一行一个整数 T T T,表示有 T T T组数据;
- 对于每组数据,第一行一个整数 N N N;
- 接下来 N N N行,每行两个整数 X , Y X , Y X,Y,代表每个核电站位置的 X , Y X , Y X,Y坐标;
- 再接下来 N N N行,每行两个整数 X , Y X , Y X,Y,代表每名特工位置的 X , Y X , Y X,Y坐标;
输出
对于每组数据,输出一个最短距离值,结果保留三位小数且每个输出结果占一行
数据范围
1 ≤ N ≤ 1 e 5 , 0 ≤ X , Y ≤ 1 e 9 1 \le N \le 1e5 , 0 \le X , Y \le 1e9 1≤N≤1e5,0≤X,Y≤1e9
题解
解法
假设先计算了某个区域内的最小距离,那么任意一对特工和核电站的坐标如果在
x
x
x或
y
y
y方向上超出了这个距离,就不用计算他们之间的距离,由此可以想到分治,先分别算出两个独立点集内的最小距离,再考虑跨越了这两个点集的最小距离,这样就可以得到两个点集合并所得点集内的最小距离
先定义一个结构体数组用于储存位置(无论核电站还是特工的),数组的每个元素包含
x
,
y
x , y
x,y坐标,以及一个标签(
b
o
o
l
bool
bool变量)用于表示该位置是核电站的还是特工的
为了方便计算跨越点集的最小距离,先将定义的结构体数组按照
x
x
x或
y
y
y值排序(本文按照
x
x
x)
这样排好序后可以采用二分的方式,每一部分先分别算好其左右两个子部分内的最小距离,比较得到二者中的较小值
a
n
s
ans
ans,那么如果想要通过跨越两个部分对
a
n
s
ans
ans产生贡献,考虑的点的
x
x
x值与二分中点的
x
x
x值之间的差距就必须低于
a
n
s
ans
ans
这样筛选出了部分点后,把这些点分为特工和核电站两个部分,分别存入两个数组,对于特工数组中的每一个元素,分别考虑在核电站数组中是否存在元素到它的距离能产生贡献(对于核电站数组中的每一个元素考虑特工数组也可以),这至少需要满足二者
y
y
y值之间的差距低于
a
n
s
ans
ans,为了快速在核电站数组中找到满足条件的元素,可以在二分求解的过程中把按照
x
x
x值排序的数组再按照
y
y
y值排序,这样对于特工数组中的每一个元素,只需要在核电站数组中找到满足条件的上下界的下标即可
由此通过一边归并排序一边计算最小值就可以得到最终的答案
代码如下:
#include<cstdio>
#include<cmath>
using namespace std;
#define il inline
#define db double
const int M = 2e5 + 5;
const int inf = 15e8;
typedef struct {
int x;
int y;
bool tag;
}Point;
Point pos[M], t1[M], t2[M];
il void pswap(Point &a, Point &b) { //交换位置
a.x^=b.x^=a.x^=b.x;
a.y^=b.y^=a.y^=b.y;
a.tag^=b.tag^=a.tag^=b.tag;
}
il db dmin(db a, db b) {
return a < b ? a : b;
}
il db dis(Point a, Point b) { //计算两点距离,同tag的点之间距离无限大
return a.tag == b.tag ? inf : sqrt((db)(a.x - b.x) * (a.x - b.x) + (db)(a.y - b.y) * (a.y - b.y));
}
il void quickSort(int l, int r) { //按照x值排序
if(l == r) return ;
if(l + 1 == r) {
if(pos[l].x > pos[r].x) pswap(pos[l], pos[r]);
return ;
}
int mid = l + r >> 1, i = l, j = mid + 1, k = 0;
quickSort(l, mid), quickSort(mid + 1, r);
while(i <= mid && j <= r) {
while(i <= mid && pos[i].x <= pos[j].x) t1[++k] = pos[i++];
while(j <= r && pos[i].x >= pos[j].x) t1[++k] = pos[j++];
}
while(i <= mid) t1[++k] = pos[i++];
while(j <= r) t1[++k] = pos[j++];
for(i = l, j = 1; j <= k; ++j, ++i) pos[i] = t1[j];
}
il db divideSolve(int l, int r) { //按照y值排序并计算答案
if(l == r) return inf;
if(l + 1 == r) {
if(pos[l].y > pos[r].y) pswap(pos[l], pos[r]);
return dis(pos[l], pos[r]);
}
int mid = l + r >> 1;
db ans = dmin(divideSolve(l, mid), divideSolve(mid + 1, r));
int i = l, j = mid + 1;
int axs = pos[mid].x, k1 = l, k2;
while(i <= mid && j <= r) {
while(i <= mid && pos[i].y <= pos[j].y) t1[k1++] = pos[i++];
while(j <= r && pos[i].y >= pos[j].y) t1[k1++] = pos[j++];
}
while(i <= mid) t1[k1++] = pos[i++];
while(j <= r) t1[k1++] = pos[j++];
k1 = k2 = 0;
for(i = l; i <= r; ++i) { //筛选并分类
pos[i] = t1[i];
if(t1[i].x > axs - ans && t1[i].x < axs + ans)
t1[i].tag ? t1[++k1] = t1[i] : t2[++k2] = t1[i];
}
if(k1 && k2) { //找上下界
t2[k2 + 1].y = inf;
for(i = 1; i <= k1; ++i) {
db mxx = dmin(inf, t1[i].y + ans), mnn = t1[i].y - ans;
if(t2[1].y < mxx) {
int p1 = 1, p2 = 1;
for(; t2[p2].y < mxx; ++p2);
if(t2[1].y <= mnn)
for(; t2[p1].y <= mnn; ++p1);
for(j = p1; j < p2; ++j) ans = dmin(ans, dis(t1[i], t2[j]));
}
}
}
return ans;
}
int main() {
int t, n, m;
scanf("%d", &t);
while(t--) {
scanf("%d", &n), m = n << 1;
for(int i = 1; i <= n; ++i) scanf("%d%d", &pos[i].x, &pos[i].y), pos[i].tag = 0;
for(int i = n + 1; i <= m; ++i) scanf("%d%d", &pos[i].x, &pos[i].y), pos[i].tag = 1;
quickSort(1, m);
printf("%.3lf\n", divideSolve(1, m));
}
return 0;
}
优化
按照
x
x
x值排序时,对于重复的元素只保留一个
对于存在特工和核电站位置相同的数据,可以在一得到
!
a
n
s
!ans
!ans时直接
r
e
t
u
r
n
0
return 0
return0
对于筛选得到的特工数组中的每一个元素,在核电站数组中找上下界时可以二分查找,并且由于这些元素是排序后筛选的,所以对于特工数组中的每个元素,上下界可以在前一元素的基础上寻找,不用每次都初始化为
0
0
0
代码如下:
#include<cstdio>
#include<cmath>
using namespace std;
#define il inline
#define db double
const int M = 2e5 + 5;
const int inf = 15e8;
typedef struct {
int x;
int y;
bool tag;
}Point;
Point pos[M], t1[M], t2[M];
il bool operator == (Point a, Point b) {
return a.tag == b.tag && a.x == b.x && a.y == b.y;
}
il bool operator != (Point a, Point b) {
return a.tag != b.tag || a.x != b.x || a.y != b.y;
}
il int quickRead() {
int x = 0, f = 1;
char c = getchar();
while((c < '0' || c > '9') && c != '-') c = getchar();
if(c == '-') f = -1, c = getchar();
while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return x * f;
}
il void pswap(Point &a, Point &b) {
a.x^=b.x^=a.x^=b.x;
a.y^=b.y^=a.y^=b.y;
a.tag^=b.tag^=a.tag^=b.tag;
}
il db dmin(db a, db b) {
return a < b ? a : b;
}
il db dis(Point a, Point b) {
return a.tag == b.tag ? inf : sqrt((db)(a.x - b.x) * (a.x - b.x) + (db)(a.y - b.y) * (a.y - b.y));
}
il int quickSort(int l, int r) { //返回值表示去重后从l到多大下标是有效值
if(l == r) return l;
if(l + 1 == r) {
if(pos[l] == pos[r]) return l;
if(pos[l].x > pos[r].x) pswap(pos[l], pos[r]);
return r;
}
int L = quickSort(l, l + r >> 1), R = quickSort((l + r >> 1) + 1, r);
int i = l, j = (l + r >> 1) + 1, k = 0;
while(i <= L && j <= R) {
while(i <= L && pos[i].x <= pos[j].x) {
if(pos[i] != t1[k]) t1[++k] = pos[i];
++i;
}
while(j <= R && pos[i].x >= pos[j].x) {
if(pos[j] != t1[k]) t1[++k] = pos[j];
++j;
}
}
while(i <= L) {
if(pos[i] != t1[k]) t1[++k] = pos[i];
++i;
}
while(j <= R) {
if(pos[j] != t1[k]) t1[++k] = pos[j];
++j;
}
for(i = l, j = 1; j <= k; ++j, ++i) pos[i] = t1[j];
return l + k - 1;
}
il db divideSolve(int l, int r) {
if(l == r) return inf;
if(l + 1 == r) {
if(pos[l].y > pos[r].y) pswap(pos[l], pos[r]);
return dis(pos[l], pos[r]);
}
int mid = l + r >> 1;
db ans = dmin(divideSolve(l, mid), divideSolve(mid + 1, r));
if(!ans) return 0; //得到0立马返回
int i = l, j = mid + 1;
int axs = pos[mid].x, k1 = l, k2;
while(i <= mid && j <= r) {
while(i <= mid && pos[i].y <= pos[j].y) t1[k1++] = pos[i++];
while(j <= r && pos[i].y >= pos[j].y) t1[k1++] = pos[j++];
}
while(i <= mid) t1[k1++] = pos[i++];
while(j <= r) t1[k1++] = pos[j++];
k1 = k2 = 0;
for(i = l; i <= r; ++i) {
pos[i] = t1[i];
if(t1[i].x > axs - ans && t1[i].x < axs + ans)
t1[i].tag ? t1[++k1] = t1[i] : t2[++k2] = t1[i];
}
if(k1 && k2) {
int p1 = 0, p2 = 0; //共用p1,p2
t2[k2 + 1].y = inf;
for(i = 1; i <= k1; ++i) {
db mxx = dmin(inf, t1[i].y + ans), mnn = t1[i].y - ans;
if(t2[p2 + 1].y < mxx) { //判断是否更新p2
for(j = k2 - p2; j != 1; ++j >>= 1) //注意k2-p2,“j!=1”防止死循环,“++j >>= 1”保证可以得到所有数
if(t2[p2 + j].y < mxx)
if(t2[(p2 += j) + 1].y >= mxx) break; //下一项超了直接退出
if(t2[p2 + 1].y < mxx) ++p2; //对j==1的特判,保证可以得到所有数
}
if(t2[p1 + 1].y <= mnn) { //判断是否更新p1
for(j = p2 - p1; j != 1; ++j >>= 1) //注意p2-p1
if(t2[p1 + j].y <= mnn)
if(t2[(p1 += j) + 1].y > mnn) break;
if(t2[p1 + 1].y <= mnn) ++p1;
}
for(j = p1 + 1; j <= p2; ++j) ans = dmin(ans, dis(t1[i], t2[j])); //注意p1+1
}
}
return ans;
}
int main() {
int t, n, m;
t1[0].x = -1, t2[0].y = -inf; //“t1[0].x = -1”用于去重,“t2[0].y = -inf”用于求下界的二分查找
t = quickRead();
while(t--) {
n = quickRead(), m = n << 1;
for(int i = 1; i <= n; ++i) pos[i] = {quickRead(), quickRead(), 0};
for(int i = n + 1; i <= m; ++i) pos[i] = {quickRead(), quickRead(), 1};
printf("%.3lf\n", divideSolve(1, quickSort(1, m)));
}
return 0;
}
打赏
制作不易,若有帮助,欢迎打赏!