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]∣ (1≤i<j≤N)为了去掉绝对值,可以将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] (1≤i<j≤N)我们给定一个整数PPP,我们只需要找出所有的cat[j]−cat[i]≤Pcat[j]-cat[i]\le Pcat[j]−cat[i]≤P,就能确定PPP在ansansans中的排名(当然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\rfloorrank≥⌊2n⌋的整数。
这个结论不难理解,可以写几个例子试一下。
为了提高时间效率,可以将二分的上边界改为ansansans中最大的元素,即cat[N−1]−cat[0]cat[N-1]-cat[0]cat[N−1]−cat[0]。
还需要注意的是,如果有多个与中位数相同的数,那么我们得不到排名正好等于⌊n2⌋\lfloor \frac n2\rfloor⌊2n⌋的数,因为此时这个整数的排名要大于⌊n2⌋\lfloor \frac n2\rfloor⌊2n⌋。当然,它也是第一个大于⌊n2⌋\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;
}
最后就是求出中位数。
再提出一个结论,二分代码中的lll最后一定移动到中位数上去,也就是第一个排名大于等于⌊n2⌋\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;
}