51 Nod 1107 斜率小于0的连线数量 (转换为归并求逆序数或者直接树状数组,超级详细题解!!!)

1107 斜率小于0的连线数量

基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题

二维平面上N个点之间共有C(n,2)条连线。求这C(n,2)条线中斜率小于0的线的数量。

二维平面上的一个点,根据对应的X Y坐标可以表示为(X,Y)。例如:(2,3) (3,4) (1,5) (4,6),其中(1,5)同(2,3)(3,4)的连线斜率 < 0,因此斜率小于0的连线数量为2。

Input

第1行:1个数N,N为点的数量(0 <= N <= 50000)
第2 - N + 1行:N个点的坐标,坐标为整数。(0 <= X[i], Y[i] <= 10^9)

Output

输出斜率小于0的连线的数量。(2,3) (2,4)以及(2,3) (3,3)这2种情况不统计在内。

Input示例

4
2 3
3 4
1 5
4 6

Output示例

2

李陶冶 (题目提供者)

题目意思:
问你斜率小于0的两点的有多少个

分析:

方法1:直接树状数组求解
这题跟poj 2352很相像
那题是求某个点左下角点的个数,本题是求某个点左上角点的个数
那题输入是由规则的(按照y的升序,y相等的话,x小的在前)
所以这题一开始也要按照这个规则先排一下序
如果我们直接求某个点左上角的点的个数的话,直接改一下那题就可以了,但是有一点麻烦的地方:
那个题处于做下角边界的情况也是统计在内的
但是这题不行
所以有点麻烦
但是我们可以转化一下思路
因为一开始按照那个排序规则是已经排好序了的
所以当前点肯定是y最大的点,那么以该点为分界点
和该点斜率大于等于0或者斜列不存在的点肯定都是位于该点的左下角的
那么这个时候所有点的数量(不包括当前点)减去当前点左下角的点的数量(包括左下角的边界)
就是斜率小于0的点的数量
即就是该点右下角点的数量(不包括边界的,边界的前面左下角算过了)

还有一个问题就是数据太大了,c数组开不了,需要离散化
所以
先将输入的数据按照x离散化一下,因为每次getsum都是传x进去的
具体操作:
数据先按照x升序,x相等则y小的放前面的规则排序
因为每次getsum都是传x进去,所以按照x优先的规则排序好
排序好之后给按照顺序给点一个编号
这个就是所谓的离散化
然后getsum传进去的就是编号
具体理解一下,编号间的大小顺序关系和x原来的大小顺序关系是一样的
只是数据的范围被压缩了,但是期间各个数据的关系是没有变的
所以是可以这样的
这个就是离散化

离散化完成之后
就是跟star那题差不多了,按照y升序,y相等则x小的放前面的规则排序
然后每次getsum的时候得到的是左下角点的数量
当前点总数(除当前点)减去当前点左下角点的数量
就是当前点右下角的数量
就是和当前点斜率小于0的点的数量
然后将每次getsum的答案加起来就是所有斜率小于0的点对的数量

#include<queue>
#include<set>
#include<cstdio>
#include <iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define max_v 500005
struct node
{
    int x,y;
    int num;
} p[max_v];
bool cmp1(node a,node b)//树状数组操作需要
{
    if(a.y!=b.y)
        return a.y<b.y;
    else
        return a.x<b.x;
}
bool cmp2(node a,node b)//离散化需要
{
    if(a.x!=b.x)
        return a.x<b.x;
    else
        return a.y<b.y;
}
int c[max_v];
int maxx;
int lowbit(int x)
{
    return x&(-x);
}
void update(int x,int d)
{
    while(x<=maxx)
    {
        c[x]+=d;
        x+=lowbit(x);
    }
}
int getsum(int x,int num)
{
    int res=0;
    while(x>0)
    {
        res+=c[x];
        x-=lowbit(x);
    }
    return num-res;//当前点总数减去该点左下角点的数量就是右下角点的数量
}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        memset(c,0,sizeof(c));
        for(int i=0; i<n; i++)
        {
            scanf("%d %d",&p[i].x,&p[i].y);
        }

        //离散化
        sort(p,p+n,cmp2);
        int k=1;
        p[0].num=k;
        for(int i=1; i<n; i++)
        {
            p[i].num=++k;
        }
        maxx=k;

        //树状数组
        sort(p,p+n,cmp1);
        long long ans=0;
        for(int i=0; i<n; i++)
        {
            ans+=getsum(p[i].num,i);//注意是i,不是i+1,因为当前点总数不包括当前点自己
            update(p[i].num,1);
        }
        printf("%I64d\n",ans);
    }
    return 0;
}
/*
题目意思:
问你斜率小于0的两点的有多少个

分析:
这题跟poj 2352很相像
那题是求某个点左下角点的个数,本题是求某个点左上角点的个数
那题输入是由规则的(按照y的升序,y相等的话,x小的在前)
所以这题一开始也要按照这个规则先排一下序
如果我们直接求某个点左上角的点的个数的话,直接改一下那题就可以了,但是有一点麻烦的地方:
那个题处于做下角边界的情况也是统计在内的
但是这题不行
所以有点麻烦
但是我们可以转化一下思路
因为一开始按照那个排序规则是已经排好序了的
所以当前点肯定是y最大的点,那么以该点为分界点
和该点斜率大于等于0或者斜列不存在的点肯定都是位于该点的左下角的
那么这个时候所有点的数量(不包括当前点)减去当前点左下角的点的数量(包括左下角的边界)
就是斜率小于0的点的数量
即就是该点右下角点的数量(不包括边界的,边界的前面左下角算过了)

还有一个问题就是数据太大了,c数组开不了,需要离散化
所以
先将输入的数据按照x离散化一下,因为每次getsum都是传x进去的
具体操作:
数据先按照x升序,x相等则y小的放前面的规则排序
因为每次getsum都是传x进去,所以按照x优先的规则排序好
排序好之后给按照顺序给点一个编号
这个就是所谓的离散化
然后getsum传进去的就是编号
具体理解一下,编号间的大小顺序关系和x原来的大小顺序关系是一样的
只是数据的范围被压缩了,但是期间各个数据的关系是没有变的
所以是可以这样的
这个就是离散化

离散化完成之后
就是跟star那题差不多了,按照y升序,y相等则x小的放前面的规则排序
然后每次getsum的时候得到的是左下角点的数量
当前点总数(除当前点)减去当前点左下角点的数量
就是当前点右下角的数量
就是和当前点斜率小于0的点的数量
然后将每次getsum的答案加起来就是所有斜率小于0的点对的数量

*/

思路二:

转换为求逆序数,然后归并求逆序

从斜率的公式入手
(xi-xj)/(yi-yj)<0 的点对的个数
现在我们先按照x升序,x相同的则y小的放前面规则排好序
假设i<j
那么xi-xj肯定是小于0的
那么斜率要求小于0,则要求yi-yj大于0
则就是求排好序之后所有的y组成的序列的逆序数
它的逆序数就是斜率小于0点对的个数!
所以先按照那个规则排好序
然后将y拿出来组成一个数组
用归并排序求数列的逆序数
归并可以求逆序的理由:

在归并排序的过程中,比较关键的是通过递归,
将两个已经排好序的数组合并,
此时,若a[i] > a[j],则i到m之间的数都大于a[j],
合并时a[j]插到了a[i]之前,此时也就产生的m-i+1个逆序数,
而小于等于的情况并不会产生。

#include<stdio.h>
#include<memory>
#include<algorithm>
using namespace std;
#define max_v 500005
typedef long long LL;
struct node
{
    int x,y;
}p[max_v];
int a[max_v];
bool cmp(node a,node b)
{
    if(a.x!=b.x)
        return a.x<b.x;
    else
        return a.y<b.y;
}
int temp[max_v];
LL ans;
void mer(int s,int m,int t)
{
    int i=s;
    int j=m+1;
    int k=s;
    while(i<=m&&j<=t)
    {
        if(a[i]<=a[j])
        {
            temp[k++]=a[i++];
        }else
        {
            ans+=j-k;//求逆序数
            temp[k++]=a[j++];
        }
    }
    while(i<=m)
    {
        temp[k++]=a[i++];
    }
    while(j<=t)
    {
        temp[k++]=a[j++];
    }
}
void cop(int s,int t)
{
    for(int i=s;i<=t;i++)
        a[i]=temp[i];
}
void megsort(int s,int t)
{
    if(s<t)
    {
        int m=(s+t)/2;
        megsort(s,m);
        megsort(m+1,t);
        mer(s,m,t);
        cop(s,t);
    }
}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        if(n==0)
            break;
        ans=0;
        for(int i=0;i<n;i++)
            scanf("%d %d",&p[i].x,&p[i].y);
        sort(p,p+n,cmp);
        for(int i=0;i<n;i++)
        {
            a[i]=p[i].y;
        }
        megsort(0,n-1);
        printf("%lld\n",ans);
    }
    return 0;
}
/*
从斜率的公式入手
(xi-xj)/(yi-yj)<0 的点对的个数
现在我们先按照x升序,x相同的则y小的放前面规则排好序
假设i<j
那么xi-xj肯定是小于0的
那么斜率要求小于0,则要求yi-yj大于0
则就是求排好序之后所有的y组成的序列的逆序数
它的逆序数就是斜率小于0点对的个数!
所以先按照那个规则排好序
然后将y拿出来组成一个数组
用归并排序求数列的逆序数
归并可以求逆序的理由:

在归并排序的过程中,比较关键的是通过递归,
将两个已经排好序的数组合并,
此时,若a[i] > a[j],则i到m之间的数都大于a[j],
合并时a[j]插到了a[i]之前,此时也就产生的m-i+1个逆序数,
而小于等于的情况并不会产生。

*/

原文地址:https://www.cnblogs.com/yinbiao/p/9470840.html

时间: 2024-10-25 21:42:09

51 Nod 1107 斜率小于0的连线数量 (转换为归并求逆序数或者直接树状数组,超级详细题解!!!)的相关文章

51NOD——N 1107 斜率小于0的连线数量

https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1107 基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题  收藏  关注 二维平面上N个点之间共有C(n,2)条连线.求这C(n,2)条线中斜率小于0的线的数量. 二维平面上的一个点,根据对应的X Y坐标可以表示为(X,Y).例如:(2,3) (3,4) (1,5) (4,6),其中(1,5)同(2,3)(3,4)的连线斜率 < 0,因

51Nod - 1107 斜率小于0的连线数量

二维平面上N个点之间共有C(n,2)条连线.求这C(n,2)条线中斜率小于0的线的数量. 二维平面上的一个点,根据对应的X Y坐标可以表示为(X,Y).例如:(2,3) (3,4) (1,5) (4,6),其中(1,5)同(2,3)(3,4)的连线斜率 < 0,因此斜率小于0的连线数量为2. Input第1行:1个数N,N为点的数量(0 <= N <= 50000) 第2 - N + 1行:N个点的坐标,坐标为整数.(0 <= Xii , Yii <= 10^9)Output

51nod 1107 1107 斜率小于0的连线数量

基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题 二维平面上N个点之间共有C(n,2)条连线.求这C(n,2)条线中斜率小于0的线的数量. 二维平面上的一个点,根据对应的X Y坐标可以表示为(X,Y).例如:(2,3) (3,4) (1,5) (4,6),其中(1,5)同(2,3)(3,4)的连线斜率 < 0,因此斜率小于0的连线数量为2. Input 第1行:1个数N,N为点的数量(0 <= N <= 50000) 第2 - N + 1行:N个点的坐标

如何编程在Revit里创建长度小于0.8mm的线

Revit不知为何出有一个奇怪的规矩,那就是无法绘制长度小于 0.8mm的长度的线. (0.8mm等于 32分之一英寸). 导致很多小的短线无法绘制. 在轻钢薄壁构件里,其厚度有的只有0.5, 有的是0.7, 均无法绘制. 这给Revit的工作带来一些局限性. 这里有一个用编程的办法来绕弯路创建小于0.8mm, 其用法确实有点难以想到. 首先我们创建一个长度放大100倍的长线. 然后给这个线添加尺寸约束,并绑定到一个参数上. 最后指定参数的值为实际长度. 最后删除标准和临时参数. 这样就可以绘制

[luogu4479][BJWC2018]第k大斜率【二维偏序+二分+离散化+树状数组】

传送门 https://www.luogu.org/problemnew/show/P4479 题目描述 在平面直角坐标系上,有 n 个不同的点.任意两个不同的点确定了一条直线.请求出所有斜率存在的直线按斜率从大到小排序后,第 k 条直线的斜率为多少. 为了避免精度误差,请输出斜率向下取整后的结果.(例如: ?1.5? = 1 , ??1.5? = ?2 ) 分析 一开始打了一个暴力,10分后来改着改着成了30分,浮点误差. 正解其实很简单,我们首先逆向思考一下,如果我们假设已经有了斜率k. 如

poj 2155 二进制0 1反转---二维树状数组

http://poj.org/problem?id=2155 上午自己搞了很久胡思乱想了很久,然后没思路-----看了论文<浅谈信息学竞赛中的"0"和"1"--二进制思想在信息学竞赛中的应用>,豁然开朗啊,,马上A掉---PE了一次o(╯□╰)o 通过论文学到的两点: 1.多维不会的时候,从一维尝试类比: 2.想法的证明,情况数不多的时候,分类讨论证明很好 #include <cstdio> #include <cstring>

hdu 3584 二进制0,1反转 三维树状数组 及三维树状数组模板

先贴自己类比二维树状数组写的三维树状数组模板: 开始的时候循环体内j=y,k=z,没写,以为自己思路错了,,,hehe..... 更高维的树状数组以此类比 const int MAXN = 100+10; int c[MAXN][MAXN][MAXN];int X,Y,Z; int N; inline int lowbit(int x){return x&(-x);} void update(int x, int y, int z, int v) { int i=x,j=y,k=z; while

51nod 1107(树状数组、逆序数)

题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1107 思路:其实就是升级版的逆序数,x坐标当作位置,y坐标当作数值val,只是可能有相等的数,稍作修改即可. #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 5e4 + 3; int lowbit(int x) { return x &

51 nod 1394 1394 差和问题(线段树)

1394 差和问题基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 有一个多重集合S(即里面元素可以有重复),初始状态下有n个元素,对他进行如下操作: 1.向S里面添加一个值为v的元素.输入格式为1 v 2.向S里面删除一个值为v的元素.输入格式为2 v 3.询问S里面的元素两两之差绝对值之和.输入格式为3 对于样例, 操作3,|1-2|+|1-3|+|2-3|=4 操作1 4之后,集合中的数字为1 2 3 4 操作3,|1-2|+|1-3|+|2-3|+|1-