油井问题

油井问题

油井
主油管道为东西向,确定主油管道的南北位置,使南北向油井喷油管道和最小。要求线性时间完成。

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的测例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值