目录
题目
不知道各位听没听说过《This War of Mine》这个游戏,不过没听说过也没有关系。简单来说,这是一个生存游戏,作为一个被战争波及到的无辜平民,你需要在战争中探索周边地区获得补给从而活下去。
现在无聊的零崎有这样一个设想,如果这把这个游戏的地图挂在Google Map上,以真实地图为背景做成一个网络游戏可能会很好玩。假设世界上所有的城市已经被分为玩家初始所在地和可探索区域两类,玩家自由选择一个初始所在地,那么作为一个机智的玩家,你应该选择哪里才能让自己被FTK(首轮击杀)的可能性最小呢?
输入
第一个数为测试轮数K。
每轮测试以N开始,下面N行,代表初始地点,每行两个整数X Y表示其坐标。接下来N行代表可探索区域,每行两个整数X Y表示其坐标。
1 ≤ N ≤ 100000. 0 ≤ X,Y ≤ 1000000000
输出
每轮测试一行,输出为初始地点和可探索区域间的最近距离,结果保留小数点后三位。
分析
分析1:最近点对问题的回顾
朴素算法,枚举,时间复杂度O(n2)。
分治算法,递推式T(n) = 2T(n/2) + O(n),时间复杂度O(nlogn)
分析2:分治算法
递归出口
最平凡的情况,区间缩小到只有一个点,此时直接返回INF,因为我们需要的肯定是两个不同的点,此类平凡情况应该返回一个无效值。
分的过程
分治算法的策略是,对于输入的所有点,按照x坐标分为左右两类,然后对左右分别求最近点对。显然,这一步要求在递归之前有一个把数据按照x坐标排序的预处理。
合并过程
充分利用子问题结果,通过子问题,我们得到了左右两侧最小的点的最小距离(设为dist)。
step1 筛选:
合并的时候一定要跨越中间分界的点,中间部分的点对一定两侧一边有一个,那么对于距离中间点超过dist的点,直接不考虑,没超过的点,加入到tmp数组。
step2 排序:
对筛选出来的点按照y排序(时间复杂度O(nlogn))后面分析3会讲怎么优化。
step3 遍历:
然后二重循环遍历所有点对,寻找最小距离。看起来二重循环是O(n2)的,但是如果有一个加速(两个点y坐标相距超过dist,内层循环可以打断),可以保证最多遍历6个点。所以这一步其实是O(n)的。为啥内层循环最多只有6个点,详见分析4。
分析3:进一步降低时间复杂度(利用程序结构和归并排序的相似性)
如果按照上面的算法 时间复杂度应该是O(nlognlogn)。
从上面的分析可以看出,主要的制约因素是那个排序,这一步看似不可避免,但是有大神发现最近点对问题的程序结构和归并排序是一样的,所以排序可以顺便做,于是就没有这一步了。也就是说每次处理完点对问题后,其实维护x的顺序已经没有用了,所以把y排序,然后merge的时候得到的是左右两个按照y排好序的点数组,merge即可。后面我两个版本的代码都有,对比一下会看得比较明显。
实测发现其实没快多少。。。。OJ上显示快了20ms左右。
分析4:为啥每个点顶多遍历6个点
这是一个几何问题。首先经过筛选之后,所有要考察的点都已经局限在了一个以分割中线为对称轴左右延展dist(子问题中最近距离)的条带里。
遍历y的时候,超过了这个距离一样会打断,所以这个条带其实是一个长方形。
最坏情况其实是此时遍历的点(蓝点)恰在中线上,那么最对会有6个点(红色),此外,不会有其他的点,可以尝试一下,把蓝点稍微偏移一下,为了保证距离不小于dist,是不可能摆出更多的点的。
分析5:如何针对本题进行变式(距离重定义)
这是最近点对问题的变式。变化之处在于只有连接两类不同的点的距离是有意义的。因此,这种问题只需要重新定义距离(修改计算距离的函数),最近点对的代码框架是不需要改变的。
为了修改距离函数,本题也需要对节点的结构体进行修改,加入一个整型量标记所属的类别。
如果输入的两个点属于同一类。距离计算函数会返回INF。因为我们想要的是不同类两点的最小距离。
解答
O(nlognlogn)版
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
#define INF 1e50
struct node {
double x, y;
int cls;
};
node a[200005], tmp[200005];
bool compareY(const node &a, const node &b) {
return a.y < b.y;
}
bool compareX(const node &a, const node &b) {
return a.x < b.x;
}
double distance(node a, node b) {
if (a.cls == b.cls) {
return INF;
}
return sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
}
double closePair(int left, int right) {
if (left == right) {
return INF;
}
int mid = (left + right) >> 1;
double dist = fmin(closePair(left, mid), closePair(mid + 1, right));
int cnt = 0;
for (int i = left; i <= right; i++) {
if (fabs(a[i].x - a[mid].x) <= dist) {
tmp[cnt++] = a[i];
}
}
sort(tmp, tmp + cnt, compareY);
for (int i = 0; i < cnt; i++) {
for (int j = i + 1; j < cnt; j++) {
if (tmp[i].y - tmp[j].y > dist) {
break;
}
dist = fmin(dist, distance(tmp[i], tmp[j]));
}
}
return dist;
}
int main(int argc, char *argv[]) {
int T;
scanf("%d", &T);
while(T--) {
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%lf%lf", &a[i].x, &a[i].y);
a[i].cls = 0;
}
for (int i = n; i < 2*n; i++) {
scanf("%lf%lf", &a[i].x, &a[i].y);
a[i].cls = 1;
}
sort(a, a + 2 * n, compareX);
printf("%.3f\n", closePair(0, 2*n - 1));
}
}
O(logn)版
#include <cstdio>
#include <cmath>
#include <algorithm>
#define INF 1e50
using namespace std;
struct node {
double x, y;
int cls;
};
node a[200005], tmp[200005];
bool compareX(const node &a, const node &b) {
return a.x < b.x;
}
double distance(node a, node b) {
if (a.cls == b.cls) {
return INF;
}
return sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
}
double closePair(int left, int right) {
if (left >= right) {
return INF;
}
int mid = (left + right) >> 1;
double cut = a[mid].x; //注意这里必须要提前记录 因为后面排序这个值可能变
double dist = fmin(closePair(left, mid), closePair(mid + 1, right));
//要实现nlogn,在这里merge
//在这里merge可以直接修改a,因为两侧按x分治已经做过了,cut已经记录,没有必要再维护x的顺序
int l = left, r = mid + 1, cnt = 0;
while(l <= mid && r <= right) {
if (a[l].y <= a[r].y) {
tmp[cnt++] = a[l++];
} else {
tmp[cnt++] = a[r++];
}
}
while(l <= mid) {
tmp[cnt++] = a[l++];
}
while(r <= right) {
tmp[cnt++] = a[r++];
}
for (int i = 0; i < cnt; i++) {
a[left + i] = tmp[i];
}
// merge完成
cnt = 0;
for (int i = left; i <= right; i++) {
if (fabs(a[i].x - cut) <= dist) {
tmp[cnt++] = a[i];
}
}
for (int i = 0; i < cnt; i++) {
for (int j = i + 1; j < cnt; j++) {
if (tmp[i].y - tmp[j].y > dist) {
break;
}
dist = fmin(dist, distance(tmp[i], tmp[j]));
}
}
return dist;
}
int main(int argc, char *argv[]) {
int T;
scanf("%d", &T);
while(T--) {
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%lf%lf", &a[i].x, &a[i].y);
a[i].cls = 0;
}
for (int i = n; i < 2*n; i++) {
scanf("%lf%lf", &a[i].x, &a[i].y);
a[i].cls = 1;
}
sort(a, a + 2 * n, compareX);
printf("%.3f\n", closePair(0, 2*n - 1));
}
}