WEEK4 周记 作业C题——二分算法_TT的神秘礼物【二分答案】

WEEK4 周记 作业C题——二分算法_TT的神秘礼物【二分答案】

一、题意

1.简述

给定一个 N 个数的数组 cat[i],并用这个数组生成一个新数组 ans[i]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(cat[i] - cat[j]),1 <= i < j <= N。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。

2.输入格式

多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5。

3.输出格式

输出新数组 ans 的中位数。

4.样例

Input

4
1 3 2 4
3
1 10 2

Output

1
8

二、算法

主要思路

采用二分答案的思路,通过对 0 0 0~ 1 0 9 10^9 109进行二分,找到排名是“中位”的数。


显然,在给定一个整数的情况下确定其排名(在 a n s ans ans数组中有多少元素小于等于这个整数)的方法至关重要,也是这个题必不可少的一环。由题意可知,
a n s [ n ] = ∣ c a t [ j ] − c a t [ i ] ∣            ( 1 ≤ i < j ≤ N ) ans[n]=|cat[j]-cat[i]|\ \ \ \ \ \ \ \ \ \ (1\le i\lt j\le N) ans[n]=cat[j]cat[i]          (1i<jN)为了去掉绝对值,可以将 c a t cat cat数组按从小到大的顺序排序,那么 a n s [ n ] = c a t [ j ] − c a t [ i ]            ( 1 ≤ i < j ≤ N ) ans[n]=cat[j]-cat[i]\ \ \ \ \ \ \ \ \ \ (1\le i\lt j\le N) ans[n]=cat[j]cat[i]          (1i<jN)我们给定一个整数 P P P,我们只需要找出所有的 c a t [ j ] − c a t [ i ] ≤ P cat[j]-cat[i]\le P cat[j]cat[i]P,就能确定 P P P a n s ans ans中的排名(当然 P P P可能本身并不在 a n s ans ans数组中),这个排名是大于等于1的。对 c a t [ j ] − c a t [ i ] ≤ P cat[j]-cat[i]\le P cat[j]cat[i]P进行变形得到: c a t [ j ] ≤ P + c a t [ i ] cat[j]\le P+cat[i] cat[j]P+cat[i]我们遍历 i i i,对每一个 i i i,二分搜索 c a t cat cat数组的 [ c a t + i + 1 , c a t + n ) [cat+i+1,cat+n) [cat+i+1,cat+n)部分,找出第一个大于 P + c a t [ i ] P+cat[i] P+cat[i] c a t [ j ] cat[j] cat[j]的位置 r r r,那么对于此时的 i i i来说, r − ( c a t + i + 1 ) r-(cat+i+1) r(cat+i+1)就是满足条件 c a t [ j ] − c a t [ i ] ≤ P cat[j]-cat[i]\le P cat[j]cat[i]P的数对个数。然后将所有的 i i i对应的个数求和就是总的个数,也是 P P P的排名。

这部分的代码:

int culrank(int p)
{//求出来的排名是所有catj-cati<=p的个数 
	int i;
	int rank=0;
	for(i=0;i<n-1;i++)
	{
		int* r=upper_bound(cat+i+1,cat+n,cat[i]+p);
			//注意这里用的是upper_bound
		rank+=(r-(cat+i+1));
	}
	return rank;
}

下面是算法的主体部分:对答案的范围进行二分
显然, P P P越小,其在 a n s ans ans数组中的排名就越往前,因此就具备了单调性,就具备了二分的条件。

提出一个结论: a n s ans ans的中位数必定是 0 0 0~ 1 0 9 10^9 109中第一个满足不等式 r a n k ≥ ⌊ n 2 ⌋ rank\ge \lfloor \frac n2\rfloor rank2n的整数

这个结论不难理解,可以写几个例子试一下。

为了提高时间效率,可以将二分的上边界改为 a n s ans ans中最大的元素,即 c a t [ N − 1 ] − c a t [ 0 ] cat[N-1]-cat[0] cat[N1]cat[0]

还需要注意的是,如果有多个与中位数相同的数,那么我们得不到排名正好等于 ⌊ n 2 ⌋ \lfloor \frac n2\rfloor 2n的数,因为此时这个整数的排名要大于 ⌊ n 2 ⌋ \lfloor \frac n2\rfloor 2n。当然,它也是第一个大于 ⌊ n 2 ⌋ \lfloor \frac n2\rfloor 2n的整数,所以并不妨碍算法逻辑。

二分答案的主代码如下:

while(r>=l)
{//找出第一个排名大于等于m的数,有可能只有大于的没有等于的 
	mid=(l+r)/2;
	rank=culrank(mid);//比mid小于等于的数的个数 
	if(rank>m) r=mid-1;
	else if(rank<m) l=mid+1;
	else if(rank==m)r=mid-1;
}

最后就是求出中位数。

再提出一个结论,二分代码中的 l l l最后一定移动到中位数上去,也就是第一个排名大于等于 ⌊ n 2 ⌋ \lfloor \frac n2\rfloor 2n的整数

这个结论也不难理解,可以写一写试一试。


三、代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#include<map>  
using namespace std;
int a[4][4001];
int cat[100001];
int n;
int culrank(int p)
{//求出来的排名是所有catj-cati<=p的个数 
	int i;
	int rank=0;
	for(i=0;i<n-1;i++)
	{
		int* r=upper_bound(cat+i+1,cat+n,cat[i]+p);
		rank+=(r-(cat+i+1));
	}
	return rank;
}
int main()
{
	while(scanf("%d",&n)!=EOF)
	{
		int i,j;
		for(i=0;i<n;i++)
			scanf("%d",&cat[i]);
		sort(cat,cat+n);
		int	l=0;
		int	r=cat[n-1]-cat[0];
		int m=(n*(n-1)/2+1)/2;//中位数的排名,也是小于等于中位数的数的个数 
		int mid;
		int rank;
		while(r>=l)
		{//找出第一个排名大于等于m的数,有可能只有大于的没有等于的 
			mid=(l+r)/2;
			rank=culrank(mid);//比mid小于等于的数的个数 
			if(rank>m) r=mid-1;
			else if(rank<m) l=mid+1;
			else if(rank==m)r=mid-1;
		}
		//最后l必定会来到第一个排名大于等于m的数
		printf("%d\n",l);
	}

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值