油井问题
主油管道为东西向,确定主油管道的南北位置,使南北向油井喷油管道和最小。要求线性时间完成。
1<= 油井数量 <=2 000 000
输入要求:
输入有油井数量行,第 K 行为第 K 油井的坐标 X ,Y 。其中, 0<=X<231,0<=Y<231 。
输出要求:
输出有一行, N 为主管道最优位置的最小值
注意:用排序做的不给分!!
测试输入
41,969978
26500,413356
11478,550396
24464,567225
23281,613747
491,766290
4827,77476
14604,597006
292,706822
18716,289610
5447,914746
测试输出
597006
大致思路
虽然图片以及描述都是二维的,但其实这个一维问题,因为我们计算的距离和横坐标没有关系,只要找到这样的一条线,所有点纵坐标与它的距离的和最小就可以了,参考中位数定理,我们算出纵坐标的中位数就行,采用分治的线性时间选择算法。(老师都明确写不让用快排做了,这道题就是想让我们练习线性时间选择算法)
#include<bits/stdc++.h>
#define MAX_SIZE 2000005
#define OK 1
#define ERROR 0
using namespace std;
const int INF = 0x3f3f3f3f;
typedef long long ll;
int all[MAX_SIZE];
int select(int l, int r, int k);
int insertSort3(int x);
int Partition(int l, int r, int p);
int main() {
//freopen("test.in", "r", stdin);
//freopen("test.out", "w", stdout);
int a, b, n = 0;
while(scanf("%d,%d", &a, &b) != EOF) { //输入数据
all[n++] = b;
}
if(n % 2 == 1){
cout << select(0, n - 1, (n + 1) / 2) << endl;
}
else {
cout << select(0, n - 1, n / 2) << endl;
}
return 0;
}
int select(int l, int r, int k) { // l为数组左下标,r为数组右下标,k为要找的第k小的数
if(r - l < 75) { //如果长度小于75直接排序并返回第k小的值
sort(all + l, all + r + 1);
return all[l + k - 1];
}
for(int i = 0; i <= (r - l - 4) / 5; i++) {
insertSort3(l + 5*i);
swap(all[l + i], all[l + 5*i + 2]);
}
int mid = select(l, l + (r - l - 4) / 5, (r - l - 4) / 10); //递归寻找中位数的值,即为k = (r - l - 4) / 10
int i = Partition(l, r, mid);
int len = i - l + 1;
if(len == k) {
return all[i];
}
else if(k < len) {
return select(l, i - 1, k);
}
else {
return select(i + 1, r, k - len);
}
}
int insertSort3(int x) { //选择查找第三小的数
for(int i = 0; i < 3; i++) {
for(int j = i + 1; j < 5; j++) {
if(all[x + j] > all[x + i]) {
swap(all[x + j], all[x + i]);
}
}
}
return 0;
}
int Partition(int l, int r, int p) {
int i, j;
for(i = l; i <= r; i++) {
if(p == all[i]) {
swap(all[i], all[l]);
break;
}
}
i = l, j = r + 1;
while (1){
while (all[++i] < p && i < r);
while (all[--j] > p);
if (i >= j)
break;
swap(all[i], all[j]);
}
all[l] = all[j];
all[j] = p;
return j;
}
这里有个小问题
Partition,快排中一次排序的函数,这个我们都知道
但是就是这个东西害我T了一个测例还半天没找出来bug…
听说那个用例是全部一样的数据
我之前的partition是这样的
int Partition(int l, int r, int p) {
for(int i = l; i <= r; i++) {
if(all[i] == p){
swap(all[i], all[l]);
break;
}
}
while(l < r) {
while(all[r] >= p && l < r) r--;
all[l] = all[r];
while(all[l] <= p && l < r) l++;
all[r] = all[l];
}
all[l] = p;
return l;
}
这是我们学数据结构时都会学到的经典快排写法,这就导致了一个问题或者说隐患,在数据全部一样时,每次只能扔掉一个的数据,退化成为O( n 2 n^2 n2)的最坏情况。
这是后来按照算法书上写的写法:
int Partition(int l, int r, int p) {
int i, j;
for(i = l; i <= r; i++) {
if(p == all[i]) {
swap(all[i], all[l]);
break;
}
}
i = l, j = r + 1;
while (1){
while (all[++i] < p && i < r);
while (all[--j] > p);
if (i >= j)
break;
swap(all[i], all[j]);
}
all[l] = all[j];
all[j] = p;
return j;
}
按照这种写法的话,保证每次循环i、j都至少挪动一个位置,即使是一样的数据也能在中间附近分开,扔掉一半数据,就解决了那个T的测例