Week4 作业 C - TT的神秘礼物【二分答案】

题目描述

TT 是一位重度爱猫人士,每日沉溺于 B 站上的猫咪频道。
有一天,TT 的好友 ZJM 决定交给 TT 一个难题,如果 TT 能够解决这个难题,ZJM 就会买一只可爱猫咪送给 TT。
任务内容是,给定一个 N 个数的数组 cat[i],并用这个数组生成一个新数组 ans[i]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(cat[i] - cat[j]),1 <= i < j <= N。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。
TT 非常想得到那只可爱的猫咪,你能帮帮他吗?

输入

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

输出

输出新数组 ans 的中位数

输入样例
4
1 3 2 4
3
1 10 2
输出样例
1
8
思路

先看数据规模,n最大能取到1e5,因此我们肯定是接受不了 O ( n 2 ) O(n^2) O(n2)的做法。而算出所有的ans数组元素,然后通过遍历寻找中位数的值,复杂度正是 O ( n 2 ) O(n^2) O(n2),这我们无法接受。既然求答案很困难,我们可以考虑是否可以验证答案,这是可以的!

中位数是个整数,并且我们很容易求出中位数的取值范围(0~(max(cat) - min(cat)))。而且,这是一个单调问题:对于取值范围内的每一个数,我们可以求出它在ans数组中的名次,根据这个名次,我们就能确定中位数是小于这个数,还是大于这个数,再或是等于这个数。这也就意味着,我们可以根据名次,来二分查找得到中位数。

于是,现在的问题只剩下一个,那就是如何计算名次呢?毕竟如果直接计算ans数组会超时。
看来,我们需要对问题进行一定的转化。对于一个数P,寻找其在ans数组中的名次,也就是寻找有多少i, j(1 <= i < j <= N),满足 ∣ c a t i − c a t j ∣ < P |cat_i-cat_j|<P caticatj<P。我们想对这个式子变形,就不可避免的要去掉绝对值。为了方便去掉绝对值,我们可以提前将cat数组排好序(如从小到大排序)。于是就可以得到: c a t j < c a t i + P cat_j < cat_i + P catj<cati+P。于是,我们便可以枚举所有的cat_i,寻找满足条件的cat_j的数量,而寻找cat_j数量的过程,我们又可以通过二分来实现。
因此,计算一个数名次的复杂度为 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n)),假设中位数的取值范围是0~m,则总复杂度为 O ( n l o g ( n ) l o g ( m ) ) O(nlog(n)log(m)) O(nlog(n)log(m)),该思路可行。

注意

本题步骤较多,最好分清层次,逐步完成。

完整代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int a[100005]; 


int findcnt(int i, int xi_p)
{
	int l=i+1, r=n;
	int m;
	if(a[l] > xi_p)	return 0;
	while(l < r)
	{
		m = (l+r)>>1;
		if(a[m] <= xi_p)	l = m+1;
		else r=m;
	}
	return l - (i+1);
}


bool judge(int p, int index)  // x的名次小于index则返回1, 反之返回0 
{
	int sum=0;
	for(int i=0;i<n-1;i++)
	{
		sum += findcnt(i, a[i]+p);
	}
	return sum < index;
}

int find(int index)
{
	int l=0, r=a[n-1] - a[0];
	int m;
	while(l<r)
	{
		m = (l+r)>>1;
		if(judge(m, index))	l = m+1;
		else r = m;
	} 
	
	return l;
}
int main ()
{
	while(~scanf("%d", &n))
	{
		for(int i=0;i<n;i++)	scanf("%d", &a[i]);
		int indexOfMid = ((n*(n-1) >> 1) + 1) >> 1;
		sort(a, a+n); 
		printf("%d\n", find(indexOfMid));
	}
	return 0;
} 
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值