【问题描述】
给出 n 个整数:X1, X2, … , XN,定义f(i,j)=∣Xi - Xj∣ (1 ≤ i < j ≤ n),于是我们可以得到C(n,2)个f(i,j)。(注意C(n,2)表示从x1…xn中取两个元素的组合数)
你的任务是找出这C(n,2)个f(i,j)中第k小的数(把f(i,j)由小到大排序后的第k个数)。
【输入格式】
包含多组测试数据,每组数据占两行,第一行为整数n 和 k,第二行包含n个整数,表示X1, X2, … , XN, ( Xi ≤ 1,000,000,000 3 ≤ n ≤ 100,000 )
【输出格式】
对于每组数据,输出一行一个整数,表示第k小的数。
【输入样例】
4 3
1 3 2 4
3 2
1 10 2
【输出样例】
1
8
【样例解释】
样例中的第一组数据:1 3 2 4,得到f(i,j)序列为:2 1 3 1 1 2,其中第3小的是1。
【数据范围】
|Xi| ≤ 1,000,000,000 , 3 ≤ n ≤ 1,00,000 , 1 ≤ k ≤ C(n,2)
题目大意:给你一个数列x,要求你求出第k小的|xi-xj|。
题目乍看上去不难,我们可以两重循环生成出所有的|xi-xj|并把他们放入一个数组(或者集合之类的容器)中,然后排序取第k个元素就是答案。
但是本题n规模很大,可达到10000,C(10000,2)=9999*10000/2=49995000,也就是说你最后算出的那个数组有接近五千万个元素,而且转存的时候时间也不堪忍受。
优化解法:先将原来的数列从小到大排序。当j>i时,有xj>xi,|xj-xi|=xj-xi;
这时统计|xj-xi|小于m的个数cnt,和k比较,如果说cnt小于k,则说明m<第k小的数ans,向更大的区间猜,由于该不等式有两个未知的元素,所以说循环其中一个元素(以循环j为例),在x1…xj-1中二分查找第一个大于等于xj-m的元素下标p,由于排序后的数组的单调性特点,所以说在xp…xj-1间的元素都满足|xj-xi|小于m。
总的时间复杂度:O(n*log(n)*log(max)) 其中max为最大的f(i,j)。
#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<vector>
#include<cstring>
#define maxn 100005
using namespace std;
typedef long long LL;
const int inf=1000000000;
int n;
LL k;
int a[maxn];
bool check(int mid)
{
LL cnt=0;
for(int j=2;j<=n;j++)
{
int p=lower_bound(a+1,a+j,a[j]-mid)-a;
cnt+=j-p;
}
if(cnt<k)return 0;
return 1;
}
void solve()
{
int A=0,B=inf,Ans;
for(int i=0;i<50;i++)
{
int m=A+B>>1;
if(check(m))
B=m,Ans=m;
else
A=m;
}
printf("%d\n",Ans);
}
int main()
{
//freopen("my.in","r",stdin);
//freopen("my.out","w",stdout);
while(scanf("%d",&n)==1)
{
cin>>k;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
solve();
}
return 0;
}