poj 2299 求逆序数 树状数组/归并排序

POJ - 2299


目录

一、用树状数组求逆序数 

二、用归并排序求逆序数


关于树状数组知识点的链接: 树状数组

 

一、用树状数组求逆序数 

1、对树状数组的理解

现在算上比较明白树状数组是什么意思了,其实C[ ]数组就相当于线段树中的线段,C[ ]表示的是有几个叶子节点(A[ ])相加。按我的理解 我就把C[ ]数组理解成线段。 而add(int x,int num)函数叫单点修改,为什么叫单点修改呢,其实意思就是在叶子节点A[x]处增加num,所以这么一修改会影响到C[ ]数组,也就是含A[x]叶子节点的线段,所以在函数中要修改C[ ]数组,维护C[ ]数组。而sum()函数的意思就简单了,sum(x)其实就是查询从叶子节点A[1]到A[x]的和(这里认为叶子节点的座标从1开始)。
A[ ]数组其实是我们在分析过程中假想的叶子节点数组,在实际应用中其实不用创建A数组。

2、用树状数组求逆序数的理解

而对于这道题呢,让我们求一段排列的逆序数,就可以用到上面树状数组的知识点来求解。首先对原始数据离散化之后(离散化的理解在下面),我们就按排列的顺序依次将元素插入到树状数组中(其实意思就是要进行单点更新)刚开始每个叶子节点都为0 意思就是序列中还没有这个元素。然后我们依次add(x,1)加入到数组中,1是什么意思呢,意思就是代表序列中有了这个元素,同时也是方便之后的sum求和。 每次add(x,1),我们就算一遍sum(x),求下标从1—x的和,所以sum(x)在这道题中的实际意义就变成了,计算我前面插入的数中小于等于x的个数, 所以i- sum( x )的意思就是大于x的个数,即逆序数!! for循环结束之后就算好了整个序列的逆序数。

为什么离散化,因为我们在用树状数组时,用add(int x,int num)函数往数组中插入num元素时,在本题中元素的数据范围很大,导致我们们就要开999,999,999大小的数组,(为什么要开这么大的,因为我们用sum(x)函数计算比x小的个数时,肯定要计算出x数值前所有的和,所以最大就要开999,999,999大小额数组)。所以我们就采用离散化的思想,将原始序列数据9,1,0,5,4转化成了5,2,1,4,3(如何转化的见下面的博客链接)。所以就不会受到很大的元素的影响了。

 

一篇很好的题解链接

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<set>
using namespace std;
const int maxn=500000+10;
int n;
struct node
{
    long long val;
    int no;
    bool operator <(const node &t1) const{
        return val<t1.val;
    }
};
node t[maxn];
int c[maxn];
int a[maxn];//存离散化之后的数据
int lowbit(int x){return x&(-x);}
void add(int x,int num)
{
    for(int i=x;i<=n;i+=lowbit(i))
        c[i]+=num;
}
int sum(int x)
{
    int ans=0;
    for(int i=x;i>0;i-=lowbit(i))
        ans+=c[i];
    return ans;
}
int main()
{
//    freopen("in.txt","r",stdin);
    ios::sync_with_stdio(false);
    while(cin>>n&&n)
    {
        memset(t,0,sizeof(struct node)*maxn);
        memset(c,0,sizeof(c));
        memset(a,0,sizeof(a));
        for(int i=1;i<=n;i++)
        {
            cin>>t[i].val;
            t[i].no=i;
        }
        /*离散化*/
        sort(t+1,t+n+1);
        for(int i=1;i<=n;i++)
            a[t[i].no]=i;
        long long ans=0;
        for(int i=1;i<=n;i++)
        {
            add(a[i],1);
            ans+=i-sum(a[i]);//计算当前序列中大于a[i]的个数
        }
        cout<<ans<<endl;
    }
    return 0;
}

 

二、用归并排序求逆序数

逆序数百度百科

归并排序-求逆序数算法 看到一篇讲的很好的用归并排序求逆序数的博客。里面讲的很清楚了。

 

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<set>
using namespace std;
const int maxn=500000+10;
int n;
long long ans;
int a[maxn];//原始数组
int b[maxn];//临时数组
long long Merge(int low,int mid,int high)
{
    int i=low,j=mid+1,k=low;
    long long num=0;
    while(i<=mid&&j<=high)
    {
        if(a[i]<=a[j])
            b[k++]=a[i++];
        else
        {
            b[k++]=a[j++];
            num+=j-k;//表示合并时之间存在的逆序对数
        }
    }
        while(i<=mid)
            b[k++]=a[i++];
        while(j<=high)
            b[k++]=a[j++];
        for(int i=low;i<=high;i++)
            a[i]=b[i];
    return num;

}
long long MergeSort(int l,int r)
{
    if(l<r)
    {
        int mid=(l+r)/2;
        long long num=0;
        num+=MergeSort(l,mid);//先分,即先排序
        num+=MergeSort(mid+1,r);
        num+=Merge(l,mid,r);//再治,即合并
        return num;
    }
    return 0;
}
int main()
{
//    freopen("in.txt","r",stdin);
    ios::sync_with_stdio(false);
    while(cin>>n&&n)
    {
        ans=0;
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        for(int i=0;i<n;i++)
            cin>>a[i];
        ans=MergeSort(0,n-1);
        cout<<ans<<endl;
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章