1. 程式人生 > >51 Nod 1107 斜率小於0的連線數量 (轉換為歸並求逆序數或者直接樹狀數組,超級詳細題解!!!)

51 Nod 1107 斜率小於0的連線數量 (轉換為歸並求逆序數或者直接樹狀數組,超級詳細題解!!!)

poj pac 分析 二維 load print 序列 type 開始

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個逆序數,
而小於等於的情況並不會產生。


*/

51 Nod 1107 斜率小於0的連線數量 (轉換為歸並求逆序數或者直接樹狀數組,超級詳細題解!!!)