タイトル
回二分法
では、2で分割された第1は、ANS - ([10 ^ {18}、{18} ^ 10] \)\の間に
チェックする方法を検討
各ANSため、各列挙\(a_iを\)二部、いくつかの検索\(a_j \)、そのこと(a_iを\回a_j <ANS \
\) 正当の各ペアの\((a_iを、a_j)\)を列挙し、(a_iを\)\と\(a_j \)すべては再びそれを見つけることだったので、最後に来て、2で割った重量なければならない
、彼らが表示されない可能性があるため(a_iを= a_j \)\、そのときのリターンがなければならない(\私はjは=)\除去する場合における
二つの配列、bは
昇順で番号入力、列B中の降順
第2時分割で、\(a_iを<0 \)ので、この時点でそれ以上の絶対値をバイナリ配列(負、負、正を用いてB、負それので、「大」である場合\(a_iを\)場合、)拡大製品\(a_iを\ GEQ 0 \)こうしての缶半分ことを保証する、アレイと、
第一の層は、2つの点、我々実際には、n個で発見された(a_iを\)\数を乗じた二十二、kに小さな多数の最小数よりも大きい、出力ので\(ANS-1 \)
複雑\(O(N \ログN \ 10 ^ {18}ログ) \)
コード。
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<iomanip>
#include<cstring>
#define R register
#define EN std::puts("")
#define LL long long
inline LL read(){
LL x=0,y=1;
char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
return y?x:-x;
}
//a:abs大的负数->abs小的负数->小正数->大正数
//b:大正数->小正数->abs小的负数->abs大的负数
int n,fir;//a[fir]是a数组中第一个非负数
LL k;
LL a[200006],b[200006];
inline int finda(int i,LL ans){
R int l=1,r=n,mid,ret=0;
while(l<=r){
mid=(l+r)>>1;
if(a[mid]*a[i]<ans) ret=mid,l=mid+1;
else r=mid-1;
}
return ret>=i?(ret-1):ret;//如果ret>=i,说明其中有一对是两个a[i]相乘得到,此时要减1
}
inline int findb(int i,LL ans){
R int l=1,r=n,mid,ret=0;
while(l<=r){
mid=(l+r)>>1;
if(b[mid]*a[i]<ans) ret=mid,l=mid+1;
else r=mid-1;
}
return (ret+i>n)?(ret-1):ret;//同理,但b是倒序
}
inline int check(LL ans){
LL nowk=0;
for(R int i=1;i<fir;i++)//负数
nowk+=findb(i,ans);
for(R int i=fir;i<=n;i++)//正数
nowk+=finda(i,ans);
nowk/=2;//去重
return nowk>=k;
}
inline int cmp(int x,int y){
return x<y;
}
int main(){
n=read();k=read();
for(R int i=1;i<=n;i++) a[i]=read();
std::sort(a+1,a+1+n,cmp);
fir=n+1;
for(R int i=1;i<=n;i++)
if(a[i]>=0){fir=i;break;}
for(R int i=1;i<=n;i++) b[n-i+1]=a[i];//存入b数组,对负数二分时使用
R LL l=-1e18,r=1e18,mid,ans;
while(l<=r){
mid=(l+r)/2;
if(check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
std::printf("%lld",ans-1);
return 0;
}