题目:
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
题目分析:
-
暴力做法,枚举i, j将数列ans计算出来,然后取它的中位数 时间复杂度和空间复杂度均为𝑂(𝑛2),无法接受。
-
所以先分析一波, 因为ans[] = abs(cat[i] - cat[j]),1 <= i < j <= N。,所以首先把绝对值去了,即j>i且cat[j] > cat[i]时ans[ ]=cat[j] -cat[i];所以在这里,先对cat数组进行从小到大的排序,就能去掉绝对值。
•要求数列ans的中位数,也就是计算每一个p=ans[k]=cat[j] -cat[i]的名次,然后根据名次进行二分,当p的名次比实际中位数的名次小时,说明p小于中位数,l=mid+1;当p的名次不比实际中位数的名次小时,说明p大于等于中位数,r=mid-1;
•(个人觉得这种方法最难的在于理解并计算p的名次)因为p=ans[k]=cat[j] -cat[i],所以p的名次也就是枚举下标i然后计算满足条件的下标j的个数。for循环遍历下标i,利用再一次的二分法构建函数find(int x,int n,int *a)找到数组a中a[j]<=[i]+p的最后一个位置,并返回,那么该p值的名次即为j-i !!!(因为还有一个基本条件j>i要满足)
(注意计算完名次后找中位数的二分的while循环条件为while(l<=r),之前写成了while(l<r)结果wrong answer了qaq)
代码:
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
int find(int x,int n,int *a)//找到数组a中j<=i+p的最后个位置
{
int l=0;int r=n-1;int ans=-1;
while(l<=r)
{
int mid=(l+r)>>1;
if(a[mid]<=x)
{
ans=mid;
l=mid+1;
}
else r=mid-1;
}
return ans;
}
int main()
{
int N;
while(scanf("%d",&N)!=EOF)
{
int *cat=new int[N];
for(int i=0;i<N;i++)
{
scanf("%d",&cat[i]);
}
sort(cat,cat+N);
int len=N*(N-1)/2;//中位数应该在的位置
int real_mid=(len+1)/2;
int l=0;
int r=cat[N-1]-cat[0];
int re;
while(l<=r)
{
int num=0;//计算每一个p>=xj-xi的j的个数,即为名次
int mid=(l+r)/2;
for(int i=0;i<N;i++)
{
int ans=find(cat[i]+mid,N,cat);
if(ans!=-1)//存在于并且找到了在数组中的最后一个位置
{
num+=(ans-i);
}
}
if(num>=real_mid)
{
re=mid;
r=mid-1;
}
else
{
l=mid+1;
}
}
printf("%d\n",re);
}
return 0;
}