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

本文介绍了一种使用二分查找算法解决特定数学问题的方法,即在一个由两两元素差值组成的数组中找到中位数。通过排序和二分搜索,算法能够高效地确定给定数值在差值数组中的排名,从而找到中位数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

二、算法

主要思路

采用二分答案的思路,通过对000~10910^9109进行二分,找到排名是“中位”的数。


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

这部分的代码:

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;
}

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

提出一个结论:ansansans的中位数必定是000~10910^9109中第一个满足不等式rank≥⌊n2⌋rank\ge \lfloor \frac n2\rfloorrank2n的整数

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

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

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

二分答案的主代码如下:

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;
}

最后就是求出中位数。

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

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


三、代码

#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、付费专栏及课程。

余额充值