洛谷 P4479 [BJWC2018]第k大斜率

洛谷 P4479 [BJWC2018]第k大斜率

洛谷传送门

题目描述

在平面直角坐标系上,有 nn 个不同的点。任意两个不同的点确定了一条直线。请求出所有斜率存在的直线按斜率从大到小排序后,第 kk 条直线的斜率为多少。

为了避免精度误差,请输出斜率向下取整后的结果。(例如:\lfloor 1.5 \rfloor = 1⌊1.5⌋=1,\lfloor -1.5 \rfloor = -2⌊−1.5⌋=−2)。

输入格式

第一行,包含两个正整数 nn 和 kk
接下来 nn 行,每行包含两个整数 x_i, y_ix**i​,y**i​,表示每个点的横纵坐标。

输出格式

输出一行,包含一个整数,表示第 kk 大的斜率向下取整的结果。


题解:

2020.12.2赛前最后一次模拟赛T3暴力场。

\(O(n^2)\)暴力的分数应该是人均分吧。

然后开始想优化。

正解应该是\(O(n\log n)\)的做法,所以考虑和2有关的算法,二分。

二分什么东西比较好呢?直接二分答案吧。对于第K大,二分答案,如果比它大的多于K个,右移区间,否则左移。

难点在于如何快速判断有多少比它大的直线。如果直接\(O(n^2)\)枚举,那二分干什么。所以,思考对判断的过程进行优化,也就是推一推性质、式子来着。

对于两个点连线所构成的斜率,假设两个点为\(i,j\),当前二分到的斜率为\(S\),如果这个斜率大于它,那么会有:

\[\frac{y_i-y_j}{x_i-x_j}\ge S \]

乘过去再移项,会有:

\[y_i-Sx_i\ge y_j-Sx_j \]

芜湖,长得就一样了呀。

所以现在对于每个点\(i\),我们都可以处理出一个\(y_i-Sx_i\),令其为\(t_i\),然后就变成了一个二维偏序问题:对于\(S\),有多少点对满足\(x_i<x_j\)\(t_i<t_j\),也就是求“顺序对”。

原理和求逆序对是一样的,我这里用的是归并排序,对于归并排序求逆序对,请看:归并排序求逆序对

代码:

#include<cstdio>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=1e5+5;
const int INF=2e8;
int n,k,ans,tmp;
struct node
{
	int x,y;
}p[maxn];
int a[maxn],t[maxn];
bool cmp(node a,node b)
{
	if(a.x==b.x)
		return a.y>b.y;
	return a.x<b.x;
}
void merge_sort(int l,int r)
{
	int mid=(l+r)>>1;
	if(l==r)
		return;
	merge_sort(l,mid);
	merge_sort(mid+1,r);
	int i=l,j=mid+1,k=l;
	while(i<=mid&&j<=r)
	{
		if(a[i]<=a[j])
		{
			tmp+=(r-j+1);
			t[k++]=a[i++];
		}
		else
			t[k++]=a[j++];
	}
	while(i<=mid)
		t[k++]=a[i++];
	while(j<=r)
		t[k++]=a[j++];
	for(int p=l;p<=r;p++)
		a[p]=t[p];
}
bool check(int x)
{
	for(int i=1;i<=n;i++)	
		a[i]=p[i].y-x*p[i].x;
	tmp=0;
	merge_sort(1,n);
	return tmp>=k;
}
signed main()
{
	scanf("%lld%lld",&n,&k);
	for(int i=1;i<=n;i++)
		scanf("%lld%lld",&p[i].x,&p[i].y);
	sort(p+1,p+n+1,cmp);
	int l=-INF,r=INF;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid))
		{
			ans=mid;
			l=mid+1;
		}
		else
			r=mid-1;
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2020-12-02 19:16  Seaway-Fu  阅读(184)  评论(0编辑  收藏  举报