Week4 作业 C - TT 的神秘礼物 POJ - 3579 二分答案

题目
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 非常想得到那只可爱的猫咪,你能帮帮他吗?

Input

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

Output

输出新数组 ans 的中位数

Sample Input

4
1 3 2 4
3
1 10 2

Sample Output

1
8
解题思路

因为序列太大了,ans[] = abs(cat[i] - cat[j]) 的总个数约为n^2,把每一个数都算出来不大现实,所以这道题需要用二分法来求解较为合适。

数组 cat[i] 中数的范围是 cat[i] <= 1e9 ,所以最后得到的中位数也是在 [0,1e9] 的范围内,所以我们可以用二分法估计某个数是不是中位数的候选数。要判断我们选出的这个数是不是中位数,我们可以计算 ans[] = abs(cat[i] - cat[j]) 中小于等于这个数的个数,如果等于 (abs数组元素个数+1)/2 那么这个数就是中位数。如果 getans(num) < (ans + 1) / 2 则说明中位数大于 num ,如果 getans(num) > (ans + 1) / 2 ,则说明中位数小于等于 num,如果 getans(num) = (ans + 1) / 2 ,则说明中位数就是 num。这些我们可以用二分法找出那个 num。

在 getans 函数中,给出一个 num ,要求计算 ans[] = abs(cat[i] - cat[j]) 中小于等于这个数的个数,因为 cat 数组有序且 i < j ,我们可以用二分法计算。从 i=0 到 i=n-2 ,对于每个 cat[i] ,找到序号最大的 cat[j],使得 cat[i] - cat[j] <= num ,那么 mid - i 就是小于 num 数的数量,将这个数与计数 count 相加,完成 i-1 次循环后,就得到了小于等于 num 数的数量。具体地,因为满足单调性,把尺取法和二分法结合,记录下每次循环后 j 的位置,下次二分查找的范围可以直接定位到 [ i , j ] 。

最后,因为中位数在区间内,如果 midresult != midrange ,得到的 mid 不一定就是中位数,因此也要判断相邻的数是否也符合中位数条件。

程序源码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<math.h>
using namespace std;

int* cat;
int n;
int candidate = 0; //可能的中位数


int ansnum() { //返回ans的总数目
	int ans = n - 1;
	int fac = n - 2;
	while (fac) ans += fac--;
	return ans;
}

int getans(int num) { //计算 <=num 的 abs(cat[i] - cat[j]) 数量
	candidate = 0;
	int count = 0; //计数清零
	int min = 0, max = 0, mid = 0;
	for (int i = 0; i < n - 1; i++) {
		min = i + 1; max = n - 1;
		while (min <= max) { //二分法获得最后一个 <=num 数的下标
			mid = (min + max) / 2;
			if (cat[mid] - cat[i] <= num) { //可能右边还有数
				min = mid + 1;
				if (candidate < cat[mid] - cat[i]) {
					candidate = cat[mid] - cat[i];
				}
			}
			else { //数在左边
				if (cat[mid] - cat[i] > num) {
					max = mid - 1;
				}

			}
		}
		if (cat[mid] - cat[i] > num) {
			mid--; //当前 mid 指向的是后一个数
		}
		count += mid - i; //统计符合条件数的数量
	}
	return count;
}

int main() {
	ios::sync_with_stdio(false);
	while (scanf("%d", &n) != EOF) { //获取结束信号

		cat = new int[n]; //存储输入的数据
		for (int i = 0; i < n; i++) {
			scanf("%d", &cat[i]);
		}
		sort(cat, cat + n); //升序排序

		int ans = ansnum(); //获得 ans[] = abs(cat[i] - cat[j]) 总组数
		int midrange = (ans + 1) / 2; //中位数的下标序号

		int min = 0, max = 1000, mid = 0; //第一次试探范围
		while (getans(max) < midrange) {
			min = max;
			max = max * 1000;
		}

		while (min <= max) { //二分法找中位数
			mid = (min + max) / 2;
			int midresult = getans(mid);
			if (midresult == midrange) {
				break;
			}
			else if (midresult < midrange) {
				min = mid + 1;
				continue;
			}
			else { //midresult > midrange
				max = mid - 1;
				continue;
			}
		}
		if (getans(mid) < midrange) { //因为中位数在区间内,
			//如果 midresult != midrange ,现在得到的 mid 不一定是中位数
			if (getans(min) >= midrange) { //因此需要查找相邻的数是否符合条件
				mid = min;
			}
			else if (getans(max) >= midrange) {
				mid = max;
			}
			else {
				getans(mid);
			}
		}
		if (candidate < mid) { //输出最后结果
			cout << candidate << endl;
		}
		else {
			cout << mid << endl;
		}

		delete[]cat; //清理工作
	}
	return 0;

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值