题目链接
任意两个雪球合成一个大雪球,大雪球的体积等于两个小雪球的体积之和,找体积第 k 小的大雪球的体积。
二分,分的是合成后大雪球的体积
找合成后的雪球体积小于中间值的组合数,大于等于k说明 体积第 k 小的大雪球的体积 在左区间,小于k说明 体积第 k 小的大雪球的体积 在右区间
在未知数组中找第k小的数
代码:
#include<iostream>
#include<algorithm>
using namespace std;
int n,k;
const long long int N=1e6;
long long int *a = new long long int[N];
bool check(long long int m);
int main()
{
int t,i;
cin>>t;
while(t--){
//数据输入
cin>>n;
for(i = 0; i<n; i++)cin>>a[i];
cin>>k;
sort(a,a+n); //升序排列
long long int l = a[0]+a[1]; //左边界
long long int r = a[n-2]+a[n-1];//右边界
while(l<r){
long long int m = (l+r)>>1; //中间值
if(check(m)){
r = m; //合成后的雪球体积小于中间值的组合数大于等于k,选左
}else{
l = m+1; //合成后的雪球体积小于中间值的组合数小于k,选右
}
}
cout<<l-1<<endl;
}
}
//找合成后的雪球体积小于中间值的组合数,大于等于k返回true,否则返回false
bool check(long long int m)
{
long long int cnt = 0;
int i,j;
int t = n-1;
//双指针遍历
for(i = 0; i<n; i++){
for(j = t; j>i; j--){
if(m > a[i]+a[j]){
cnt += j-i; //j从大到小,a[j]也从大到小,即数组是升序的,下标减小数组值也减小
//当a[j]满足条件时,a[j-1],a[j-2]……也满足条件,j-i是满足条件的组合数
break;
}
}
t = j; //每循环一次i增加,eg:若m<a[1]+a[8],则m<a[2]+a[8]一定成立
//所以下一轮t从j(当前轮次满足满足m > a[i]+a[j])开始即可
}
if(cnt>=k)return true;
else return false;
}
失败的尝试:
想通过标记出等于中间值的,减少二分次数
但 中间值不一定在目标数组中
eg: 假设目标数组为 2 3 5 6 (2+6)/2 = 4 不在目标数组中
#include<iostream>
#include<algorithm>
using namespace std;
int n,k;
const long long int N=1e6;
long long int *a = new long long int[N];
int check(long long int m);
int main()
{
int t,i;
cin>>t;
long long int m;
while(t--){
cin>>n;
for(i = 0; i<n; i++)cin>>a[i];
cin>>k;
sort(a,a+n);
long long int l = a[0]+a[1];
long long int r = a[n-2]+a[n-1];
while(l<r){
m = (l+r)>>1;
if(check(m) == -1){
r = m;
}else if(check(m) == 1){
l = m+1;
}else break;
}
if(check(m) != 0)cout<<l-1;
else cout<<m;
}
}
int check(long long int m)
{
long long int cnt = 0;
int i,j;
int t = n-1;
for(i = 0; i<n; i++){
for(j = t; j>i; j--){
if(m > a[i]+a[j]){
cnt += j-i;
break;
}
}
t = j;
}
if(cnt>k)return -1;
else if(cnt<k)return 1;
else return 0;
}