题解:P4479 [BJWC2018] 第k大斜率
P4479 题解
题面
前置知识
- 树状数组求逆序对。(不会的看后记。)
- 二维偏序。(不会的可以利用这道题学一学。)
- 知道斜率公式。
思路
首先,O(n2) 的大家肯定都没有问题。
接下来就开始考虑正解,首先答案是从大到小第 k 大的斜率,满足单调性,就可以二分答案。
那二分答案也很好想,只要判断有几个斜率大于等于 mid 即可,接下来就开始推式子。
首先,一个直线(不妨设 xi<xj)的斜率大于 num 得满足如下式子。
yi−yjxi−xj>num
yi−yj>num×(xi−xj)
yi−num×xi>yj−num×xj
咱们令 ti=yi−num×xi,于是问题就转化为满足 xi<xj,ti>tj 的数对的个数。
于是这就是二维偏序的问题。
接下来就是要把二维降维成一维偏序。(就是求逆序对。)其实也很简单啦,只要将 x 数组提前从小到大排序一下(那么前提条件 xi<xj 就满足了)就可以啦。
实现细节
- 二分的边界,这个其实挺好想,就是斜率最大的时候为 ymax−ymin1=2×108。
- 对输入的点进行排序的时候 y 的关键字。(如果错了就喜提 40 分的好成绩。)当 xi=xj 时,这时候斜率是不存在的,所以它不能被计入答案,所以要 yi>yj。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
const int MN=1e5+5;
ll n,k,t[MN],ans,rk[MN];
struct point{ll x,y;}a[MN];
struct node{ll val,id;}q[MN];
void write(ll n){if(n<0){putchar('-');write(-n);return;}if(n>9)write(n/10);putchar(n%10+'0');}
ll read(){ll x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
ll lowbit(ll x){return x&-x;}
void change(ll x, ll v){while(x<=n){t[x]+=v;x+=lowbit(x);}}
ll query(ll x){ll res=0;while(x){res+=t[x];x-=lowbit(x);}return res;}
bool cmp1(point a, point b){return a.x==b.x?a.y>b.y:a.x<b.x;}
bool cmp2(node a, node b){return a.val==b.val?a.id>b.id:a.val>b.val;}
bool check(ll num){
ll res=0;
memset(t,0,sizeof(t));
for(int i=1; i<=n; i++) q[i].id=i,q[i].val=a[i].y-num*a[i].x;//降维。
sort(q+1,q+1+n,cmp2);
for(int i=1; i<=n; i++) rk[q[i].id]=i;//常规离散化。
for(int i=n; i>=1; i--){//常规求逆序对。
res+=query(rk[i]-1);
change(rk[i],1);
}
return res>=k;
}
int main(){
n=read();k=read();
for(int i=1; i<=n; i++) a[i].x=read(),a[i].y=read();
sort(a+1,a+1+n,cmp1);//按照x的升序,y的降序排序。
ll l=-2e8,r=2e8;//见实现细节
while(l<=r){//二分答案。
ll mid=l+r>>1;
if(check(mid)) l=mid+1,ans=mid;
else r=mid-1;
}
write(ans);putchar('\n');
return 0;
}
后记
这里来介绍下树状数组求逆序对。(虽然原题的题解可能会更好。)
首先我们先理解下逆序对的本质是求 i<j,ai>aj 的 (i,j) 的个数。
接下来开始介绍算法。
用树状数组接逆序对需要的一个思想(或者说技巧),把数字看做树状数组的下标。接下来就是一次处理每一个元素,树状数组的下标所对应的元素数值加 1,统计前缀和,这就是逆序对的数量。倒序和正序处理均可,这里介绍倒序。
倒序依次处理每一个数,当前数字的前一个数的前缀和即为以该数为较大数的逆序对个数。
当然,一般求逆序对的时候为了防止因为数据太大,会选择离散化,因为求逆序对只需要每对数的相对大小,这样就可以用树状数组维护了。
代码如下。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
const int MN=1000005;
ll n,m,x,rk[MN],t[MN],ans;
struct node{ll id,val;}a[MN<<2];
ll read(){ll x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
ll lowbit(ll x){return x&-x;}
void change(ll x, ll v){while(x<=n){t[x]+=v;x+=lowbit(x);}}
ll query(ll x){ll res=0;while(x){res+=t[x];x-=lowbit(x);}return res;}
bool cmp(node x, node y){if(x.val==y.val)return x.id<y.id;return x.val<y.val;}
int main(){
n=read();
for(int i=1; i<=n; i++) a[i].val=read(),a[i].id=i;
sort(a+1,a+1+n,cmp);
for(int i=1; i<=n; i++) rk[a[i].id]=i;//离散化
ans=0;
for(int i=n; i>0; i--){//倒序求逆序对
change(rk[i],1);
ans+=query(rk[i]-1);
}
printf("%lld\n",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现