树状数组专题(一)-最少的交换(树状数组,离散化,逆序数)

树状数组专题(一)-最少的交换(树状数组,离散化,逆序数)

在这里插入图片描述

  1. 算法思路:求逆序数的和

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

    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

  2. 离散化

    • 现在由于999999999这个数字相对于500000这个数字来说是很大的,
      所以如果用数组位存储的话,那么需要999999999的空间来存储输入的数据。
      这样是很浪费空间的,题目也是不允许的,所以这里想通过离散化操作,
      使得离散化的结果可以更加的密集。
      简言之就是开一个大小为这些数的最大值的树状数组

    • 离散化是一种常用的技巧,有时数据范围太大,可以用来缩放我们能处理的范围;

      离散化其实就是建立一个给定数与1到N数组的一个映射

      1. 当然用map可以建立,效率可能低点

      2. 这里用一个结构体

        struct Node{
            int val;	// 数值
            int pos;	// 原始下标
        }p[510000];	// 和一个数组a[510000]
        

        其中val就是原输入的值,pos是下标,然后对结构体按val进行排序。

        for(int i = 1 ; i < N ; i++)
            a[p[i].pos] = i;	// 排序后,原来的下标和现在的下标(已有序)一一对应,由于原来的下标与数值用结构体绑定,即原来的数值与现在有序的下标一一对应。即a[i]中存放原来未排序数值应该存放的下标
        
      3. 离散后,怎么使用离散后的结果数组来进行树状数组操作,计算出逆序数?

        如果数据不是很大, 可以一个个插入到树状数组中,
        每插入一个数, 统计比他小的数的个数,
        对应的逆序为 i- sum( a[i] ),
        其中 i 为当前已经插入的数的个数,
        sum( a[i] )为比 a[i] 小的数的个数,
        i- sum( a[i] ) 即比 a[i] 大的个数, 即逆序的个数

  3. 代码如下

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    const int maxn = 500000+10;
    using namespace std;
    
    struct Node{
        int val;
        int pos;
    }p[maxn];
    
    int a[maxn];	// 离散化后的数组,即真正需要操作的数组
    int c[maxn];	// 树状数组
    
    int lowbit(int x){
        return x & (-x);
    }
    
    int getSum(int x){
        int sum = 0;
        for(int i = x ; i > 0 ; i -= lowbit(i))
            sum += c[i];
        return sum;
    }
    
    void update(int x,int val){
        for(int i = x ; i < maxn ; i += lowbit(i)){
            c[i] += val;
        }
    }
    
    bool cmp(Node a,Node b){
        return a.val < b.val;
    }
    
    int main(){
        long n;
        while(scanf("%ld",&n)!=EOF && n){
            memset(c,0,sizeof(c));
            for(int i = 1 ; i <= n ; i++){
                scanf("%ld",&p[i].val);
                p[i].pos = i;	
            }
            sort(p+1,p+n+1,cmp);
            for(int i = 1 ; i <= n ; i++){
                a[p[i].pos] = i;
            }
           	long long ans = 0;
            for(int i = 1 ; i <= n ; i++){
                update(a[i],1);
                ans += i - getSum(a[i]);
            }
            printf("%lld\n",ans);
        }
    }
    
  4. 运行结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值