逆序对

求逆序对的个数
codevs题号:1688、4163(都是求逆序对) 洛谷题号: P1908 ,poj 2299。

题目描述:
对于数列{a},如果有序数对(I,j)满足:i< j,a[i]>a[j],则(i,j)是一对逆序对。
给定一个数列{a},求逆序对个数。
(输入数据较大,请使用scanf代替cin读入。)

输入描述 Input Description
第一行一个数n,表示{a}有n个元素。

接下来n个数,描述{a}。

输出描述 Output Description
一个数,表示逆序对个数。

样例输入 Sample Input
5

3 1 5 2 4

样例输出 Sample Output
4

数据范围及提示 Data Size & Hint
对于10%数据,1<=n<=100.

对于20%数据,1<=n<=10000.

对于30%数据,1<=n<=100000.

对于100%数据,1<=n<=1000000,1<=a[i]<=10^8.

题解:
这题要求逆序对的个数,是要我们求位置靠后的数在它之前有多少比它大的。那么我们可以反过来想想,逆序对个数=这个数的前面有多少个数–在它之前有多少比它小的数,那怎么求在它之前有多少比它小的数呢? 前缀和嘛,对不对。那么在求前缀和过程中,怎么最快呢? 树状数组是个不错的选择。

这里说一下数据离散化操作,我们想想会发现,无论数据数值多大都对结果无影响,主要是看它于其前的数谁大谁小,不是吗?

那么怎么对这个输入的数组进行离散操作?
离散化是一种常用的技巧,有时数据范围太大,可以用来放缩到我们能处理的范围;
因为其中需排序的数的范围0—10^8
而N的最大范围是1000000;故给出的数一定可以与1………N建立一个一一映射;
(1)当然用map可以建立,效率可能低点;
(2)这里用一个结构体
struct Node
{
int value,pos;
}p[510000];和一个数组a[510000];

其中value就是原输入的值,pos是下标;
然后对结构体按value从小到大排序 (注意value相同时,要按下标大小排,为什么要这样,代码处会说).

此时,value和结构体的下标就是一个一一对应关系,
而且满足原来的大小关系;

for(i=1;i<=N;i++)
a[p[i].pos]=i;

然后a数组就存储了原来所有的大小信息;
比如 9 1 0 5 4 ——- 离散后a数组
就是 5 2 1 4 3;
具体的过程可以自己用笔模拟一下就好了。

接下来上c++代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm> 
using namespace std;
long long  int n,reflect[1000010],c[1000010];//reflect数组用来保存原来序列中每个数的大小信息,c数组为树状数组 
long long ans=0; 
struct node{
    long long int value;//数值 
    long long  int  pos;//一开始的下标(处于原序列的位置) 
}a[1000010]; 
bool cmp(node a,node b)//比较函数 
{
    return a.value ==b.value?a.pos<b.pos:a.value<b.value;//这里是三目运算符,注意数值相等时,一定要按下标从小到大排序 
}
long long int lowbit(long long int i)
{
    return i&(-i);//求i用2的幂方和表示时的最小幂 
}
void update(long long int i)//更新树状数组 
{
    while(i<=n)
    {
        c[i]+=1;
        i+=lowbit(i);
    }
}
long long int getsum(long long int i)//求前i个数的和(前缀和) 
{
    long long int h=0;
    while(i>=1)
    {
        h+=c[i];//这里前缀和加上了自己,所以下求ans时是ans+=i-getsum(reflect[i]),而不是ans+=i-getsum(reflect[i])-1. 
        i-=lowbit(i);
    }
    return h;
}
int main()
{
    memset(c,0,sizeof(c));//树状数组初始化为0 
    memset(reflect,0,sizeof(reflect));
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i].value);
        a[i].pos=i; 
    }
    sort(a+1,a+n+1,cmp);//排序 
    for(int i=1;i<=n;i++)
    {
        if(a[i].value==a[i-1].value)            //重点来了,为什么数值相等时,一定要按下标从小到大排序  
        reflect[a[i].pos]=reflect[a[i-1].pos];  //因为这里是按前一个数的大小来确定位置,而前一个数又是用下标pos确定的 
        reflect[a[i].pos]=reflect[a[i-1].pos]+1;
    }  
    for(int i=1;i<=n;i++)
    {
        update(reflect[i]);//压入树状数组 
        ans+=i-getsum(reflect[i]);//ans=当前已经插入的数的个数-在它之比它小的数的个数,为什么是当前已经插入的数的个数(是加上了自己的),而不是这个数的前面有多少个数呢,因为我们用getsum函数求前缀和时也加上了自己啊 
    }
    cout<<ans;//输出 
    return 0;
}   

提一下,离散化是为了
Poj2299 “ 树状数组求逆序对的个数”准备的。
因为poj2299那道题里,由于9 9999 9999这个数字(题目有说)相对于500000这个数字来说是很大的,所以如果用数组位存储的话,那么需要999999999的空间来存储输入的数据。
这样是很浪费空间的,题目也是不允许的,所以这里想通过离散化操作,
使得离散化的结果可以更加的密集。
简言之就是开一个大小为这些数的最大值的树状数组。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值