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\n",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\n",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次进入循环,执行第一个赋值时的 左 右 指 针 分 别 为 l i , r i 左右指针分别为l_i,r_i li,ri,执行第二个赋值时的 左 右 指 针 分 别 为 l i + 1 , r i 左右指针分别为l_{i+1},r_i li+1,ri,一共进入k次循环。
如果我们把赋值看作有向边的话,很明显可以想出,图应该是这样的:
l 1 &lt; −   r 1 &lt; − l 2 … … &lt; − l k &lt; − r k &lt; l k + 1 ( &lt; − 表 示 有 向 边 ) l_1&lt;- \ r_1 &lt;- l_2……&lt;-l_k&lt;-r_k&lt;l_{k+1}(&lt;-表示有向边) l1< r1<l2<lk<rk<lk+1<
最后,我们把 l 1 l_1 l1的初值赋值给 l k + 1 l_{k+1} lk+1就相当于对(2k+1)个数进行了交换。同时也把[l,r]分成不大于基准值和不小于基准值的两块。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值