树状数组求逆序数(离散化)+归并排序求逆序数

前言

1、什么是逆序数?

         在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序数的总数就是这个排列的逆序数。

2、用树状数组求逆序数的总数

         2.1该背景下树状数组的含义

         我们假设一个数组A[n],当A[n]=0时表示数字n在序列中没有出现过,A[n]=1表示数字n在序列中出现过。A对应的树状数组为c[n],则c[n]对应维护的是数组A[n]的内容,即树状数组c可用于求A中某个区间的值的和。

         树状数组的插入函数(假设为 void insert(int i,int x) )的含义:在求逆序数这个问题中,我们的插入函数通常使用为insert( i , 1 ),即将数组A[i]的值加1 (A数组开始应该初始化为0,所以也可以理解为设置A[ i ]的值为1,即将数字i 加入到序列的意思 )。,同时维护c数组的值。

         树状数组中区间求和函数(假设函数定义为: int getsun(int i ) )的含义:该函数的作用是用于求序列中小于等于数字 i 的元素的个数。这个是显而易见的,因为树状数组c 维护的是数组A的值,则该求和函数即是用于求下标小于等于 i 的数组A的和,而数组A中元素的值要么是0要么是1,所以最后求出来的就是小于等于i的元素的个数。

         所以要求序列中比元素a大的数的个数,可以用i - getsum(a)即可( i 表示此时序列中元素的个数)。

离散化+树状数组

算法的大体流程就是:

扫描二维码关注公众号,回复: 2391555 查看本文章

1.先对输入的数组离散化,使得各个元素比较接近,而不是离散的,

2.接着,运用树状数组的标准操作来累计数组的逆序数。

算法详细解释:

1.解释为什么要有离散的这么一个过程?

    刚开始以为999.999.999这么一个数字,对于int存储类型来说是足够了。

    还有只有500000个数字,何必要离散化呢?

    刚开始一直想不通,后来明白了,后面在运用树状数组操作的时候,

    用到的树状数组C[i]是建立在一个有点像位存储的数组的基础之上的,

    不是单纯的建立在输入数组之上。

    比如输入一个9 1 0 5 4,那么C[i]树状数组的建立是在,

    下标 0 1 2 3 4 5 6 7 8 9

    数组 1 1 0 0 1 1 0 0 0 1

    现在由于999999999这个数字相对于500000这个数字来说是很大的,

    所以如果用数组位存储的话,那么需要999999999的空间来存储输入的数据。

    这样是很浪费空间的,题目也是不允许的,所以这里想通过离散化操作,

    使得离散化的结果可以更加的密集。

第一种离散化。(包含重复元素,并且相同元素离散化后也要相同,推荐使用)
离散化以前一直搞不懂是怎么实现的,看了一个代码才明白。


const int maxn=1e5+10;

int a[maxn], t[maxn];

int n;

scanf("%d",&n);

for(int i=1; i<=n; i++)

    scanf("%d",a[i]),t[i]=a[i];

sort(t+1,t+n+1);

m=unique(t+1,t+1+n)-t-1;//m为不重复的元素的个数

for(int i=1; i<=n; i++)

    a[i]=lower_bound(t+1,t+1+m,a[i])-t;

原来的a[i]离散化后成了后来的a[i];

离散化后的a[i]范围是(1-m);
举个栗子:
原序列:6 9 4 6 4
排序后:4 4 6 6 9
unique(元素去掉重复的)后:4 6 9 6 9  (前m位数字无重复,(自己瞎猜的:其他数字跟sort排序后的序列相比不改变 )


#include <cstdio>

#include <algorithm>

using namespace std;

int a[10]={6,9,4,6,4};

int main()

{

    int n=5;

    sort(a,a+n);//排序后4 4 6 6 9

    n=unique(a,a+n)-a;

    for(int i=0;i<5;i++)

        printf("%d ",a[i]);

    printf("\n");

    //最后输出4 6 9 6 9, 我猜是重复的只留一个,后面的数组没变,保留原来的数字

}

unique有一个返回值,例如有十个有序的数列3 3 5 5 6 6 6 7 7 8,不重复的数字有五个,使用unique去重之后数列变成了3 5 6 7 8 6 6 7 7 8,它只改变了前五个数字后边的不变,返回值是 最后一个改变的数字的地址。so:m=unique(t+1,t+1+n)-t-1;一般要减去首地址(t+1),m为不重复的数字的个数

  第二种离散化(复杂度低,1.包含重复元素,并且相同元素离散化后不相同,2.不包含重复元素,并且不同元素离散化后不同,符合这两种的其中一个,推荐使用


struct A

{

    int x, idx;

    bool operator < (const A &rhs) const

    {

        return x < rhs.x;

    }//也可以写个cmp函数排序

};

A a[MAXN];

int rank[MAXN];

int n;

scanf("%d",&n);

for(int i = 1; i <= n; ++i)

{

    scanf("%d", &a[i].x);

    a[i].idx = i;

}

//for(int i=1; i<=n; i++)

//    printf("%d  %d\n",a[i].idx,a[i].x);

//printf("\n");

sort(a + 1, a + n + 1);

//for(int i=1; i<=n; i++)

//    printf("%d  %d\n",a[i].idx,a[i].x);

//printf("\n");

for(int i = 1; i <= n; ++i)

{

    rank[a[i].idx] = i;

//    printf("rank[%d] = %d\n",a[i].idx,i);

}

给你们个例子:
i      1 2 3 4
x     6 8 9 4
idx  1 2 3 4
排序后:

i      1 2 3 4  //离散化后的数
x     4 6 8 9 
idx  4 1 2 3  //数原来的所在的位置编号
将上面两行黑体数字对应起来 即是:rank[4]=1,rank[1]=2,rank[2]=3,rank[3]=4;  //rank[i]=j表示将原来在第i个位置上的数字离散化成j
so:
rank[1]=2;
rank[2]=3;
rank[3]=4;
rank[4]=1;
so:   6 8 9 4

就离散化为2,3,4,1
如果你想用原来的数字,就用排过序的结构体a[2]=6,a[3]=8,a[4]=9,a[1]=4; //a[i]=j表示排过序后现在的a数组里第i个数字的是j。j也就是第i小。

a[i]是原来的数字;
rank是离散化后的数字;

下面给出三个求逆序数的代码两个是树状数组(第一种离散化和第二种离散化的变形),一个是归并排序

树状数组(第一种离散化)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
int n,tree[100010];
void add(int k,int num)
{
    while(k<=n)
    {
        tree[k]+=num;
        k+=k&-k;
    }
}

int read(int k)
{
    int sum=0;
    while(k)
    {
        sum+=tree[k];
        k-=k&-k;
    }
    return sum;
}

int main()
{
    int i,j,x,y;
    int b[100010];
    int a[100010];
    while(scanf("%d%d%d",&n)!=EOF)
    {
        LL sum = 0;
        memset(tree,0,sizeof(tree));
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
           b[i]=a[i];
        }
        sort(b+1,b+n+1);
        int cnt = 1;

        int m=unique(b+1,b+n+1)-b-1;//m为不重复的元素的个数

        for(int i=1; i<=n; i++)
            a[i]=lower_bound(b+1,b+m+1,a[i])-b;


        for(i=1;i<=n;i++)
        {
            add(a[i],1);
            sum += (LL)(i-read(a[i]));//统计当前序列中大于a[i]的元素的个数
        }

        printf("%lld\n",sum);

    }
    return 0;
}

树状数组(第二种离散化的变形)

因为求逆序数时,存在相同元素,并且相同元素离散化后也要相同

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
int n,tree[100010];
void add(int k,int num)
{
    while(k<=n)
    {
        tree[k]+=num;
        k+=k&-k;
    }
}

int read(int k)
{
    int sum=0;
    while(k)
    {
        sum+=tree[k];
        k-=k&-k;
    }
    return sum;
}
struct node
{
    int val,pos;
}a[100010];
bool cmp(node a,node b)
{
    return a.val < b.val;
}
int main()
{
    int i,j,x,y;
    int b[100010];
    while(scanf("%d",&n)!=EOF)
    {
        LL sum = 0;
        memset(tree,0,sizeof(tree));
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i].val);
            a[i].pos = i;
        }
        sort(a+1,a+1+n,cmp);
        int cnt = 1;
        
        for(i=1;i<=n;i++)//离散化
        {
            if(i != 1 && a[i].val != a[i-1].val)
                cnt++;
            b[a[i].pos] = cnt;
        }
        

        for(i=1;i<=n;i++)
        {
            add(b[i],1);
            sum += (LL)(i-read(b[i]));//统计当前序列中大于b[i]的元素的个数
        }

        printf("%lld\n",sum);
        
   
    }
    return 0;
}

归并排序求逆序数

给出一个序列,只交换相邻两数,使得序列升序排列,求出最少交换次数。

思路:

如果说只是交换相邻两个数字。那么就是这个序列的逆序数。

1.假设序列个数为n,我们先把最大的数换到最后,因为是相邻数字交换,所以把最大数交换到最后,需要交换的次数为最大数后的数字个数。

2.当完成最大数的交换后,可以将最大数从序列中划去不管了,即此时序列个数为n-1了,我们再在该序列中找到一个最大数,进行相同操作。

3.所以使整个序列有序的交换次数为,这个序列的所有逆序总数。

比如4,3,2,1。
(4,3) (4,2) (4,1),有3个逆序,交换后 3,2,1,4
(3,2) (3,1),有2个逆序,交换后2,1,3,4
(2,1),有1个逆序,交换后1,2,3,4

用归并的方法写了个。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
using namespace std;
int cnt;
int arr[1005];
void Merge(int* arr,int* tmp,int left,int right,int rightEnd){
    int leftEnd = right - 1;
    int start = left;
    while (left <= leftEnd && right <= rightEnd){
        if (arr[left] > arr[right]){
            tmp[start++] = arr[right++];
            cnt += (leftEnd - left+1); //如果当前left位置上的数大于right位置上的数,那么从left到leftEnd所有的数都大于
        }
        else{
            tmp[start++] = arr[left++];
        }
    }
    while (left <= leftEnd){
        tmp[start++] = arr[left++];
    }
    while (right <= rightEnd){
        tmp[start++] = arr[right++];
    }
}
void MergeSort(int* arr,int* tmp,int n,int length)//length当前有序子列的长度
{
    int i;
    for (i = 0; i <= n - 2 * length; i += 2 * length){
        Merge(arr,tmp,i,i+length,i+2*length-1);
    }
    //最后剩下两个子列,进行归并
    if (i + length < n){
        Merge(arr,tmp,i,i+length,n-1);
    }
    else{//只剩最后一个子列,不能成对
        for (int j = i; j < n; j++){
            tmp[j] = arr[j];
        }
    }
}
void Merge_Sort(int* arr,int n){
    int lenght = 1;
    int* tmp = (int *)malloc(sizeof(int)*n);
    while (lenght < n){
        MergeSort(arr,tmp,n,lenght);
        lenght *= 2;
        MergeSort(tmp,arr,n,lenght);
        lenght *= 2;
    }
    free(tmp);
}
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF){
        memset(arr,0,sizeof(arr));
        for(int i=0;i<n;i++){
            scanf("%d",&arr[i]);
        }
        cnt=0;
        Merge_Sort(arr,n);
        printf("%d\n",cnt);
    }
    return 0;
}

还有一个队友写的树状数组求逆序数

先将价值大到小排序并且如果价值相同按坐标从大到小排序

最后只需要求query(ccc)即可(query(ccc)表示统计当前序列中大于ccc的元素个数)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define minn(x,y,z) min(min(x,y),z)
struct node
{
    int x,i;
    bool operator < (const node &a) const
    {
        if(x!=a.x)
            return x>a.x;
        return i>a.i;
    }
}p[100005];
int c[100005];
int lowbit(int k)
{
    return k&(-k);
}

void add(int k)
{
    while(k < 100005)
    {
        c[k] += 1;
        k += lowbit(k);
    }
}

int query(int k)
{
    int sum = 0;
    while(k)
    {
        sum += c[k];
        k -= lowbit(k);
    }
    return sum;
}

int main(){
    int n;
    while(~scanf("%d",&n))
    {
        memset(c,0,sizeof(c));
        rep(i,1,n) scanf("%d",&p[i].x),p[i].i=i;

        sort(p+1,p+1+n);

        ll ans = 0x3f3f3f3f,cnt = 0, cn = 0;

        for(int i=1;i<=n;i++)
        {
            int ccc =p[i].i;
            cnt = cnt + query(ccc);
            add(ccc);
        }
        printf("%d\n",cnt);
    
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41021816/article/details/81204529