铺设油井管道: 某石油公司有n口油井,为方便输送石油,计划修建输油管道。根据设计要求,水平方向有一条主管道,每口油井修一条垂直方向的支线管道通向主管道。请设计一种算法确定主管道的位置,使得所有油井到主管道之间的支线管道长度的总和最小。
输入格式:
每个输入文件为一个测试用例,每个文件的第一行给出一个正整数n(1≤n≤1000000),表示油井数量,从第二行起的n行数据,表示每口油井的位置,每行包含以空格分隔的两个整数,分别表示每口油井的横坐标x(−10000≤x≤10000)和纵坐标y(−10000≤y≤10000)。
输出格式:
输出各油井到主管道之间的支管道最小长度总和。
输入样例:
在这里给出一组输入。例如:
4
-1 1
2 2
5 -1
-3 7
输出样例:
9
题目分析:
要想得到各油井到主管道之间的支管道最小长度总和,则主管道最优位置y应该是所有管道位置yi的中位数,则最小长度之和为每个管道|yi-y|的长度之和。
算法:
1.直接求中位数:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
int x;
int y[1000];
cin>>n;
for(int i=0;i<n;i++)
cin>>x>>y[i];
sort(y,y+n); //按升序排序
int min=0;
int mid=y[n/2];
for(int i=0;i<n;i++)
min += (int)fabs(y[i]-mid);
cout<<min<<endl;
}
输出样例:
2.使用分治法求中位数:
分治法:
1.分治法是对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同。
2。递归地解这些子问题,然后将各子问题的解合并得到原问题的解。
分治法的基本步骤:
代码:
#include <stdio.h>
#include <stdlib.h>
void swap(int &a,int &b)
{
int t=a;
a=b;
b=t;
}
int quick(int y[],int l,int h) //快速排序,返回数轴位置
{
int x=y[l];
while(l<h)
{
while(l<h&&y[h]>x) h--;
swap(y[l],y[h]);
while(l<h&&y[l]<x) l++;
swap(y[l],y[h]);
}
y[l]=x;
return l;
}
int middle(int y[],int l,int h,int k)
{
if(l==h) return y[l];
int i=quick(y,l,h);
int j=i-l+1; //表示这个数轴是整个数组中第几小的.
if(k<=j){
return middle(y,l,i,k); //如果比第k小的数要大,则在比它小的数中中在找第k小的
}
else return middle(y,i+1,h,k-j); //如果比第k小的数要小,则只需要在比它大的数中找,第k-j大的
}
int main()
{
int n;
scanf("%d",&n);
int x[10000],y[10000];
for(int i=0;i<n;i++){
scanf("%d %d",&x[i],&y[i]);
}
int mid;
mid=middle(y,0,n-1,n/2+1); //寻找中位数
int sum=0;
for(int i=0;i<n;i++){
sum+=abs(mid-y[i]);
}
printf("%d",sum);
}
输出结果:
分析:
可以使用分治法将原问题分为若干个子问题。先使用quick()返回出第一个数轴的位置l。代表这个数是整个数组中第l+1小的,而我们需要寻找第k=n/2小的,即中位数。用此数轴位置将整个数组分为两个部分,则左边半边都是<y[l]的,右边都是>y[l]的,在分别在这两半边寻找中位数,以此类推。最后当l=h时,找到数组纵坐标的中位数,返回y[l]。
其中在middle函数中,当l=h时返回 y[l]
j=l+1表示为在和数轴中第j小的。若第j小的比第k小的数要大则return middle(y,l,i,k)
反之则return middle(y,i+1,h,k-j)
进行递归,最后得出中位数。再进行相加,最后即可得出答案。