洛谷 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;
}