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
二、算法
主要思路
采用二分答案的思路,通过对 0 0 0~ 1 0 9 10^9 109进行二分,找到排名是“中位”的数。
显然,在给定一个整数的情况下确定其排名(在
a
n
s
ans
ans数组中有多少元素小于等于这个整数)的方法至关重要,也是这个题必不可少的一环。由题意可知,
a
n
s
[
n
]
=
∣
c
a
t
[
j
]
−
c
a
t
[
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)为了去掉绝对值,可以将
c
a
t
cat
cat数组按从小到大的顺序排序,那么
a
n
s
[
n
]
=
c
a
t
[
j
]
−
c
a
t
[
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)我们给定一个整数
P
P
P,我们只需要找出所有的
c
a
t
[
j
]
−
c
a
t
[
i
]
≤
P
cat[j]-cat[i]\le P
cat[j]−cat[i]≤P,就能确定
P
P
P在
a
n
s
ans
ans中的排名(当然
P
P
P可能本身并不在
a
n
s
ans
ans数组中),这个排名是大于等于1的。对
c
a
t
[
j
]
−
c
a
t
[
i
]
≤
P
cat[j]-cat[i]\le P
cat[j]−cat[i]≤P进行变形得到:
c
a
t
[
j
]
≤
P
+
c
a
t
[
i
]
cat[j]\le P+cat[i]
cat[j]≤P+cat[i]我们遍历
i
i
i,对每一个
i
i
i,二分搜索
c
a
t
cat
cat数组的
[
c
a
t
+
i
+
1
,
c
a
t
+
n
)
[cat+i+1,cat+n)
[cat+i+1,cat+n)部分,找出第一个大于
P
+
c
a
t
[
i
]
P+cat[i]
P+cat[i]的
c
a
t
[
j
]
cat[j]
cat[j]的位置
r
r
r,那么对于此时的
i
i
i来说,
r
−
(
c
a
t
+
i
+
1
)
r-(cat+i+1)
r−(cat+i+1)就是满足条件
c
a
t
[
j
]
−
c
a
t
[
i
]
≤
P
cat[j]-cat[i]\le P
cat[j]−cat[i]≤P的数对个数。然后将所有的
i
i
i对应的个数求和就是总的个数,也是
P
P
P的排名。
这部分的代码:
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;
}
下面是算法的主体部分:对答案的范围进行二分
显然,
P
P
P越小,其在
a
n
s
ans
ans数组中的排名就越往前,因此就具备了单调性,就具备了二分的条件。
提出一个结论: a n s ans ans的中位数必定是 0 0 0~ 1 0 9 10^9 109中第一个满足不等式 r a n k ≥ ⌊ n 2 ⌋ rank\ge \lfloor \frac n2\rfloor rank≥⌊2n⌋的整数。
这个结论不难理解,可以写几个例子试一下。
为了提高时间效率,可以将二分的上边界改为 a n s ans ans中最大的元素,即 c a t [ N − 1 ] − c a t [ 0 ] cat[N-1]-cat[0] cat[N−1]−cat[0]。
还需要注意的是,如果有多个与中位数相同的数,那么我们得不到排名正好等于 ⌊ n 2 ⌋ \lfloor \frac n2\rfloor ⌊2n⌋的数,因为此时这个整数的排名要大于 ⌊ n 2 ⌋ \lfloor \frac n2\rfloor ⌊2n⌋。当然,它也是第一个大于 ⌊ n 2 ⌋ \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;
}
最后就是求出中位数。
再提出一个结论,二分代码中的 l l l最后一定移动到中位数上去,也就是第一个排名大于等于 ⌊ n 2 ⌋ \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;
}