suxxsfe

一言(ヒトコト)

题解 AT4867 【[ABC155D] Pairs】

题目
两次二分
首先对ans进行二分,在\([-10^{18},10^{18}]\)之间
考虑怎么check
对于每个ans,枚举每个\(a_i\),二分查找有几个\(a_j\),使得\(a_i\times a_j<ans\)
对于每对合法的\((a_i,a_j)\),枚举到\(a_i\)\(a_j\)的时候都被找了一遍,所以最后应该除以2来去重
因为不能出现\(a_i=a_j\),所以返回的时候要把\(i=j\)的情况去掉
两个数组a,b
将输入的数在a中按升序排序,在b中按降序排
在第二次二分时,当\(a_i<0\),用b数组二分(负负得正,所以此时绝对值更大的负时是“大”的,因为它与\(a_i\)的乘积更大),当\(a_i\geq 0\),用a数组,这样保证了可二分性
通过第一层二分,我们找到的其实是在n个\(a_i\)两两相乘得到的数中,比从小到大第k个数大的最小数,所以要输出\(ans-1\)
复杂度\(O(n\log n\log 10^{18})\)
code.

#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;
}
posted @ 2020-03-19 21:55  suxxsfe  阅读(154)  评论(0编辑  收藏  举报