CH0501 货仓选址(第k小数)

描述
在一条数轴上有N家商店,它们的坐标分别为 A[1]~A[N]。现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。
输入格式
第一行一个整数N,第二行N个整数A[1]~A[N]。
输出格式
一个整数,表示距离之和的最小值。
样例输入
4
6 2 9 1
样例输出
12
数据范围与约定
对于100%的数据: N<=100000, A[i]<=1000000

这是一道裸的中位数题,但本人不想用快排,于是用了求第k小数的做法(不完全排序)。
方法1(快排):

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int n,a[N],p;long long ans;
#define  g getchar()
void qr(int &x)
{
	char c=g;x=0;bool v=0;
	while(!(('0'<=c&&c<='9')||c=='-'))c=g;
	if(c=='-')v=1,c=g;
	while('0'<=c&&c<='9')x=x*10+c-'0',c=g;
	if(v)x=-x;
}
int main()
{
	qr(n);
	for(int i=0;i<n;i++)qr(a[i]);
	sort(a,a+n);
	p=(n>>1)-1;
	for(int i=0;i<p;i++)ans+=a[p]-a[i];
	for(int i=p+1;i<n;i++)ans+=a[i]-a[p];
	printf("%lld
",ans);
	return 0;
}

方法2(第k小数):

#include<cstdio>
#include<ctime>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define g getchar()
using namespace std;
const int N=1e5+10;
void qr(int &x)
{
	char c=g;x=0;
	while(!('0'<=c&&c<='9'))c=g;
	while('0'<=c&&c<='9')x=x*10+c-'0',c=g;
}
int a[N],n,k;
int random(int x){return rand()%x;}//取[0,x-1]中的数 
int find(int l,int r)//查基准值排名 
{
	int p=random(r-l+1)+l,x=a[p];//随机基准值。
	swap(a[l],a[p]);
	while(l<r)
	{
		while(a[r]>=x&&l<r)--r;
		a[l]=a[r];
		while(a[l]<=x&&l<r)++l;
		a[r]=a[l];
	}
	a[l]=x;
	return l;
}
int dfs(int l,int r)//返回第k小数 
{
	int t=find(l,r);
	if(t<k)return dfs(t+1,r);
	else if(t==k)return a[t];
	else return dfs(l,t-1);
}
int main()
{
	srand(time(0));
	qr(n);k=(n+1)>>1;
	for(int i=1;i<=n;i++)qr(a[i]);
	int p=dfs(1,n);
	long long ans=0;
	for(int i=1;i<k;i++)ans+=p-a[i];
	for(int i=k+1;i<=n;i++)ans+=a[i]-p;
	printf("%lld
",ans);
	return 0;
}

这是运用分治的思想。
但是,可能大家会不理解find函数为什么就能把[l,r]分成不大于基准值和不小于基准值的两块。不要急,且听我娓娓道来~~

int find(int l,int r)//查基准值排名 
{
	int p=random(r-l+1)+l,x=a[p];//随机基准值。
	swap(a[l],a[p]);
	while(l<r)
	{
		while(a[r]>=x&&l<r)--r;
		a[l]=a[r];
		while(a[l]<=x&&l<r)++l;
		a[r]=a[l];
	}
	a[l]=x;
	return l;
}

首先,我们可以看出x就是选出的随机基准值。
次之,我们可以用赋值环来解释这样做的正确性。
设第i次进入循环,执行第一个赋值时的li,ri左右指针分别为l_i,r_i,执行第二个赋值时的li+1,ri左右指针分别为l_{i+1},r_i,一共进入k次循环。
如果我们把赋值看作有向边的话,很明显可以想出,图应该是这样的:
l1&lt; r1&lt;l2&lt;lk&lt;rk&lt;lk+1&lt;l_1&lt;- r_1 &lt;- l_2……&lt;-l_k&lt;-r_k&lt;l_{k+1}(&lt;-表示有向边)
最后,我们把l1l_1的初值赋值给lk+1l_{k+1}就相当于对(2k+1)个数进行了交换。同时也把[l,r]分成不大于基准值和不小于基准值的两块。

原文地址:https://www.cnblogs.com/zsyzlzy/p/12373924.html