zoukankan      html  css  js  c++  java
  • 树状数组求逆序对

    数值统计:任意给定一个集合$a$,如果用$t[val]$保存数值$val$在集合中出现的次数,那么数组$t$在$[l,r]$上的区间和(即$sum_{i=l}^{r} t[i]$)就表示集合$a$中范围在$[l,r]$内的数有多少个。

        我们可以在集合$a$的数值范围上建立一个树状数组,来维护$t$的前缀和。这样即使在集合$a$中插入或删除一个数,也可以高效的进行统计。

        我们已知逆序对问题可以通过归并排序的方法求解,对于一个序列$a$,若$i < j$且$a[i] > a[j]$,则称$a[i]$与$a[j]$构成逆序对。利用上面数值统计的思路可以得到另一种解法:

    1. 在序列$a$的数值范围上建立树状数组,初始化为全零。
    2. 倒序扫描给定的序列$a$,对于每个数$a[i]$:
      • 在树状数组中查询前缀和$[1,a[i]-1]$,累加到答案$ans$中
      • 执行“单点增加”操作,即把位置$a[i]$上的数加1(相当于$t[a[i]]++$),同时正确维护$t$的前缀和。这表示数值$a[i]$又出现了1次。

    核心代码:

     for(int i = n;i >=1;i--)
     {
         ans += sum(a[i] - 1);
         add(a[i], 1);
     }

        在这个算法中,因为是倒序扫描,“已经出现过的数”就是在$a[i]$后边的数,所以我们通过树状数组查询的内容就是“每个$a[i]$后边有多少个比它小”。每次查询的结果之和当然就是逆序对的个数。时间复杂度为$O((N+M)logM)$,$M$为数值范围的大小。

        当数值范围较大时,当然可以先进行离散化,再用树状数组进行计算。不过因为离散化本身就要通过排序来实现,所以在这种情况下就不如直接用归并排序来计算逆序对数了。

  • 相关阅读:
    toj 2975 Encription
    poj 1797 Heavy Transportation
    toj 2971 Rotating Numbers
    zoj 2281 Way to Freedom
    toj 2483 Nasty Hacks
    toj 2972 MOVING DHAKA
    toj 2696 Collecting Beepers
    toj 2970 Hackle Number
    toj 2485 Card Tric
    js页面定位,相关几个属性
  • 原文地址:https://www.cnblogs.com/lfri/p/11083493.html
Copyright © 2011-2022 走看看