(题目请参考poj3579)
却说上回说到的魔法照相馆的主人是一位长者,他曾是过去陪同勇者作战的法师,而现如今的和平年代,他也不用上战场了,就在这里开了家照相馆,来接待过往的探险家们给他们提一些建议。
这天,长者特意邀请勇者过来尝试他最新的通灵魔法,大致的内容如下:
“……这是一个开发你自身潜力的魔法……我们通过感应器接收到来自你身体内部的灵气,并转化成数据,此时按照统计魔法来计算就会得到m=C(N,2)个答案,这个时候我们找到第(m+1)/2个小的数据,即是你需要开通灵气的地方,此时我们的魔法就成功了…………”
然而,面对勇者这样的单细胞生物来说,他的身上没有太多灵气,所以直接暴力枚举就能完成,不过这位长者想要为更多想成为魔法师的普通人开通灵气,因此所得到的数据很多,而且因为来照相馆的人很多,所以要快速解决。
很显然,这就需要用到路由器魔法师导论的精髓————二分魔法了。
(下列话语请依据程序(魔法)来看)
首先,我们完全可以立刻算出m进而算出我们要求的第几位数字——k,然后将初始数排序,这是我们最开始就应该做的。
然后,为了加快速度,所以我们采用二分的思想,但在我们写二分之前,我们介绍一个函数。
upper_bound(a+1,a+n+1,c)-a;
这个函数是用来计算从a【1】~a【n】之间的数字比c小的数的个数(而且很快)(咱们就不深究这个高深的魔法了)
然后我们改进一下ans+=t-i-1;(这里的ans是指有多少个中位数数是比mid小),那么t-i-1就是表示以第i的数为起点开始计算有多少个中位数数是比mid小的个数。
好的,我们来开始说一下二分的思想了。
对,二分答案。
我们知道答案的范围一定在0~a[n]-a[1](还记得咱们已经排过序了吗)
所以我们在这中间找答案,然后利用upper_bound找到比mid小的中位数的个数。
接下来就是咱们激动人心的特判环节了。
第一个特判没什么好说的,就是一个终止条件。
接下来我们看看最重要的两个特判。
如果我们求得的个数大于k的话,我们知道mid一定是过大了,于是suan(l,mid);
反之就是过小了,亦然。
那么我们来看一下困扰路由器多年的等号究竟应该放在哪里,是要单独拿出来特判吗,不行!(虽然不知道为什么但是过不了是肯定的——题外话),那么我们将这个并入一个特判里吧!
放在哪里呢?
我们不妨模拟下吧,我们假设得到了正确答案mid的话,那么这就意味着接下来所有的答案都是错误的,那么这也就是说;
当此时处于(l,mid)之间的时候,我们之后找到的所有的数都要比k小,那么将会一直执行最后一个特判,这样最终的得到的r就一定是正确答案;
反之的话,我们发现mid此时已经+1了,所以说永远也不可能得到正确答案了。
于是,我们决定,将等号放在了第二个特判表达式里
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
#include<iostream>
using namespace std;
int a[100002]={0};
int c,k;
int n;
void suan(int l,int r){
if(l==r){
printf("%d\n",l);
return;
}
int ans=0;
int mid=(l+r)/2;
for(int i=1;i<=n;i++){
int t=upper_bound(a+1,a+n+1,a[i]+mid)-a;
ans+=t-i-1;
}
if(ans>=k)suan(l,mid);
else if(ans<k)suan(mid+1,r);
}
int main(){
while(cin>>n){
memset(a,0,sizeof(a));
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
c=n*(n-1)/2;
k=(c+1)/2;
sort(a+1,a+n+1);
suan(0,a[n]-a[1]);
}
return 0;
}