离散化+树状数组 求逆序对数

本文介绍如何使用树状数组高效求解逆序对数量,通过离散化处理大范围整数,实现Ultra-QuickSort算法中排序操作次数的计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

除了归并排序,也可以用树状数组求逆序对数。先看 POJ 2299
Ultra-QuickSort
Time Limit: 7000MS Memory Limit: 65536K
Total Submissions: 32606 Accepted: 11633

Description

In this problem, you have to analyze a particular sortingalgorithm. The algorithm processes a sequence of n distinctintegers by swapping two adjacent sequence elements until thesequence is sorted in ascending order. For the inputsequence

9 1 0 5 4 ,

Ultra-QuickSort produces the output
0 1 4 5 9 .

Your task is to determine how many swap operations Ultra-QuickSortneeds to perform in order to sort a given input sequence.

Input

The input contains several test cases. Every testcase begins with a line that contains a single integer n< 500,000 -- the length of the input sequence. Eachof the the following n lines contains a single integer 0 ≤ a[i] ≤999,999,999, the i-th input sequence element. Input is terminatedby a sequence of length n = 0. This sequence must not beprocessed.

Output

For every input sequence, your program prints asingle line containing an integer number op, the minimum number ofswap operations necessary to sort the given input sequence.

Sample Input

5
9
1
0
5
4
3
1
2
3
0

Sample Output

6
0
 
这里面a[i]的范围是0到999,999,999,太大了,所以要先用离散化缩小范围提高效率(就跟上次那个map的映射差不多,不需要记下来每个数是多大,只要把它排第几那个位置记下来就行了,不影响求逆序对数)。离散化的方法可以是先用结构体记下这个数的大小和初始位置,再对大小进行排序(初始位置跟着大小一起排了),由此得到一个大小有序的数列,同时知道每个数的初始位置。然后用一个数组就可以记住原来每个数的大小应该排在的位置了。假设用b这个数组存,node结构体表示数(node.v是值,node.index是这个数的位置),则排序后如果这个数和上个数不相等,有b[node[i].index]=i,如果相等就是b[node[i].index]=b[node[i-1].index]。这样离散化完成了。

  那什么是树状数组呢?百度百科上的图

  这个貌似有点不好理解,反正记得这些性质:
1.设每个数二进制末尾0的个数是k,则每个数管的数个数就是2的k次方,并且管的最后一个就是自己那个数。比如8的2进制是1000,有3个0,2的3次方是8,则c[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]=a[4]+a[6]+a[7]+a[8]。所以用这种方法求和的话就只用算几个点,不用一个一个加了,节省时间。
2.这个数加上这个数管的个数等于下个管他的数。
  怎么算k呢?下面是百度来的=。= :

算这个2^k有一个快捷的办法,定义一个函数如下即可:
int lowbit(int x){
return x&(x^(x–1));
}
利用机器补码特性,也可以写成:
int lowbit(int x){
return x&(-x);
}

这两个是一样的,^是其中一个是真一个是假就是真,&是两个都真才是真,第一个我也不会解释离散化+树状数组 <wbr>求逆序对数,第二个负数就是取反,加1,比如2是10,取反是01,加1是10,10&10是10,就是2,x&(-x)就是k的值,这个还是很好理解的。。

 然后就可以用这个性质构造树状数组了~按照刚才映射的b数组,每加入一个数就计算此时已经加入的比它大的数的个数,也就是c数组里面加入它的位置的后面有多少个已经存在的数。这也等于总共多少个减去前面有多少个(包括它自己)~
 构造树状数组的方法:假设要把c[i]加1,不光c[i]要加1,管他的那些都要加1。这个数加完1后把这个数加上这个数管的数的个数就是下一个要加1的数。貌似有点绕~哦呵呵。。离散化+树状数组 <wbr>求逆序对数

代码来咯~(有百度参考别人的)
#include<stdio.h>
#include<string.h>
int b[500010],c[500010],N;
typedef struct NNode
{
    int index,v;
} Node;
Node node[500010];
int cmp(const void *a,const void *b)
{
    return (*((Node *)a)).v-(*((Node *)b)).v;
}
int lowbit(int x)  //算k
{
    return x&(-x);
}
void add(int i,int v)  //构造树状数组
{
    while(i<=N)
    {
        c[i]+=v;
        i+=lowbit(i);
    }
}
int sum(int i) //算前面已经有多少个数(包括它自己)
{
    int s=0;
    while(i>0)
    {
        s+=c[i];
        i-=lowbit(i);
    }
    return s;
}
int main()
{
    freopen("D:\\CodeBlocks\\file\\in.txt","r",stdin);
    while(scanf("%d",&N),N)
    {
        int i;
        memset(b,0,sizeof(b));
        memset(c,0,sizeof(c));
        for(i=1; i<=N; i++)
        {
            scanf("%d",&node[i].v);
            node[i].index=i;
        }
        qsort(node+1,N,sizeof(node[1]),cmp); //离散化
        b[node[1].index]=1;
        for(i=2; i<=N; i++)
            if(node[i].v!=node[i-1].v)b[node[i].index]=i;
            else b[node[i].index]=b[node[i-1].index];
        long long ans=0;
        for(i=1; i<=N; i++)
        {
            add(b[i],1);
            ans+=sum(N)-sum(b[i]);
        }
        printf("%lld\n",ans);
    }
    return 0;
}



<think>好的,我现在需要帮助用户了解如何使用树状数组进行排列相关的操作。首先,我得回顾一下树状数组的基本概念和功能。树状数组主要用于高效处理动态前缀和查询和单点更新,时间复杂度都是O(log n)。用户提到的排列问题,可能涉及到逆序对的计算,这在树状数组的应用中比较常见。 根据用户提供的引用资料,特别是引用[2]和[4],里面提到了树状数组处理逆序对的方法,以及可能需要离散化数据。逆序对是排列中的一个重要概念,例如在排序算法中评估数组的有序程度。因此,用户可能想知道如何利用树状数组来高效计算排列中的逆序对数量。 接下来,我需要整理实现步骤。首先,通常处理逆序对需要将原始数组进行离散化,尤其是当元素值范围较大时,这样可以减少内存占用并提高效率。离散化步骤包括排序、去重,然后为每个元素分配一个排名。 然后,逆序对的计算通常是从后往前遍历数组,对于每个元素,查询树状数组中比当前元素小的元素数量,这即为当前元素右侧比它小的元素数目,累加起来就是总的逆序对数目。这里需要将每个元素插入到树状数组中,并更新相应的位置。 还需要考虑树状数组的更新和查询操作。每次处理一个元素时,先查询当前树状数组中已经存在的比它小的元素的数量,这通过sum(x-1)实现,然后将当前元素的位置更新到树状数组中,执行update(x, 1)。 用户提供的示例代码中,可能涉及到这些步骤。例如,引用[4]中的离散化和完整代码部分,应该展示了如何处理这些步骤。需要确保代码示例正确,并且符合树状数组的基本操作。 此外,用户可能对树状数组的其他排列相关操作感兴趣,比如动态维护排列中的某些特性,或者处理更复杂的查询。但根据引用内容,主要应用在逆序对的计算上,所以应该围绕这个核心展开。 需要验证示例代码的正确性,确保离散化处理正确,树状数组的更新和查询函数正确实现。例如,在离散化过程中,原数组可能包含重复元素,此时需要去重,并为每个元素分配正确的排名。在Python中,可以使用字典来存储每个元素对应的排名。 最后,生成相关问题时要围绕树状数组的应用,可能涉及逆序对的变种问题离散化的必要性,以及与其他数据结构的对比,比如线段树。这样可以帮助用户进一步深入理解树状数组在排列问题中的应用。</think>### 树状数组实现排列相关操作详解 树状数组(Fenwick Tree)在排列问题中主要用于高效计算**逆序对**,并能处理动态更新的数据。核心思路是通过离散化将排列元素映射到紧凑区间,再通过树状数组的快速查询和更新特性统计逆序对数量。 --- #### **一、逆序对计算步骤** 1. **离散化处理** 将原始数组元素映射到$[1, n]$的连续整数区间,减少内存占用并保持大小关系: ```python def discretize(arr): sorted_arr = sorted(arr) unique = sorted(set(sorted_arr)) rank = {v: i+1 for i, v in enumerate(unique)} # 从1开始编号 return [rank[x] for x in arr] ``` 2. **树状数组操作** 从右向左遍历离散化后的数组,利用树状数组统计右侧比当前元素小的元素数量: ```python class FenwickTree: def __init__(self, size): self.n = size self.tree = [0] * (self.n + 1) def update(self, idx, delta): while idx <= self.n: self.tree[idx] += delta idx += idx & -idx def query(self, idx): res = 0 while idx > 0: res += self.tree[idx] idx -= idx & -idx return res def count_inversions(arr): discretized = discretize(arr) max_rank = max(discretized) ft = FenwickTree(max_rank) inversions = 0 for x in reversed(discretized): inversions += ft.query(x - 1) # 统计右侧比x小的元素数量 ft.update(x, 1) # 将当前元素加入树状数组 return inversions ``` --- #### **二、核心原理** - **离散化**:将任意数值范围压缩到连续区间,避免树状数组空间浪费[^4] - **逆序对统计**:通过从右向左遍历,每次查询已插入元素中比当前元素小的数量,累计得到总逆序对数。时间复杂度$O(n \log n)$ - **树状数组优势**:相比线段树,树状数组代码更简洁,常数更小[^1] --- #### **三、应用场景** 1. 动态维护排列中的逆序对数量(如元素实时更新) 2. 排列的奇偶性(逆序对数量模2) 3. 扩展问题:二维偏序问题、区间统计问题[^3] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值