题解:P4479 [BJWC2018] 第k大斜率

P4479 题解

题面

原题传送门

前置知识

  1. 树状数组求逆序对。(不会的看后记。)
  2. 二维偏序。(不会的可以利用这道题学一学。)
  3. 知道斜率公式。

思路

首先,O(n2) 的大家肯定都没有问题。

接下来就开始考虑正解,首先答案是从大到小第 k 大的斜率,满足单调性,就可以二分答案。

那二分答案也很好想,只要判断有几个斜率大于等于 mid 即可,接下来就开始推式子。

首先,一个直线(不妨设 xi<xj)的斜率大于 num 得满足如下式子。

yiyjxixj>num

yiyj>num×(xixj)

yinum×xi>yjnum×xj

咱们令 ti=yinum×xi,于是问题就转化为满足 xi<xj,ti>tj 的数对的个数。

于是这就是二维偏序的问题。

接下来就是要把二维降维成一维偏序。(就是求逆序对。)其实也很简单啦,只要将 x 数组提前从小到大排序一下(那么前提条件 xi<xj 就满足了)就可以啦。

实现细节

  1. 二分的边界,这个其实挺好想,就是斜率最大的时候为 ymaxymin1=2×108
  2. 对输入的点进行排序的时候 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;
}
posted @   naroto2022  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
花开如火,也如寂寞。