树状数组+离散化求逆序数poj2299Ultra-QuickSort

本文介绍了一种特定的排序算法Ultra-QuickSort,并使用树状数组来高效地计算序列排序所需的交换次数。通过实例说明了如何利用树状数组优化逆序数的计算,从而减少时间复杂度。

Description

In this problem, you have to analyze a particular sorting algorithm. The algorithm processes a sequence of n distinct integers by swapping two adjacent sequence elements until the sequence is sorted in ascending order. For the input sequence
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-QuickSort needs to perform in order to sort a given input sequence.

Input

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

Output

For every input sequence, your program prints a single line containing an integer number op, the minimum number of swap 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

逆序数就是数中各位在它前面有多少个数比它大,求出这些元素个数之和。

 

今天看了个树状数组,可以很好的解决这个问题,普通方法需要O(N^2)复杂度,用树状数组只需要O(NlongN)

 

树状数组实际上还是一个数组,只不过它的每个元素保存了跟原来数组的一些元素相关的结合值。

若A为原数组,定义数组C为树状数组。C数组中元素C[ i ]表示A[ i –lowbit( i ) + 1]至A[ i ]的结合值。

lowbit(i)是i的二进制中最后一个不为零的位数的2次方,可以这样计算

lowbit(i)=x&(-x)

lowbit(i)=x&(x^(x-1))

 

 

 

当想要查询一个sum(n)时,可以依据如下算法即可:

step1: 令sum = 0,转第二步; step2: 假如n <= 0,算法结束,返回sum值,否则sum = sum + Cn,转第三步; step3:  令n = n – lowbit(n),转第二步。

n = n – lowbit(n)这一步实际上等价于将n的二进制的最后一个1减去。而n的二进制里最多有log(n)个1,所以查询效率是log(n)的。

 

修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。所以修改算法如下(给某个结点i加上x):

step1: 当i > n时,算法结束,否则转第二步; step2: Ci = Ci + x, i = i + lowbit(i)转第一步。

i = i +lowbit(i)这个过程实际上也只是一个把末尾1补为0的过程。

 

求逆序的思路:

 

可以把数一个个插入到树状数组中, 每插入一个数, 统计比他小的数的个数,对应的逆序为 i- getsum( data[i] ),其中 i 为当前已经插入的数的个数, getsum( data[i] )为比 data[i] 小的数的个数,i- getsum( data[i] ) 即比 data[i] 大的个数, 即逆序的个数。最后需要把所有逆序数求和,就是在插入的过程中边插入边求和。

 

下面是代码:

 
#include 
  
     
#include 
   
      
#include
    
     
#include
     
      
using namespace std;  
struct  lisan{
	int a;
	int i;
}a[500005];
int b[500005],tree[500005];
int read(int k)
{  
    int sum=0;  
    while(k)  
    {  
        sum+=tree[k];  
        k-=k&-k;
    }
    return sum;
}  
void add(int k,int num,int n)  
{  
    while(k<=n)  
    {  
        tree[k]+=num;  
        k+=k&-k;  
    }  
} 
bool cmp(lisan a,lisan b){  
    return a.a
      
     
    
   
  
<think>好的,我需要解决用户关于Ultra-QuickSort算法的问题,包括如何实现和解释它。首先,我得先理解这个算法的本质。根据用户提供的引用,Ultra-QuickSort实际上是通过交换相邻元素来排序,而问题是要计算需要多少次这样的交换操作。听起来这类似于计算逆序对的数量,因为每次交换相邻元素只能消除一个逆序对。 接下来,我需要回忆一下相关的算法。通常,计算逆序对的高效方法有归并排序和树状数组。用户提到的引用[3]中提到了归并排序,这可能是一个常用的方法。归并排序在分治的过程中可以统计逆序对的数量,具体来说,在合并两个有序子数组时,如果左边的元素大于右边的元素,那么左边的剩余元素都会与这个右边的元素形成逆序对,这样就能统计出数量。 那么,如何将归并排序应用于这个问题呢?首先,归并排序会将数组分成两半,递归地对左右两部分排序,并统计各自的逆序对数目。然后在合并过程中,当左边的元素大于右边的元素时,就需要计算左边剩余元素的数量,并将这个数量到总的逆序对数目中。这样,总的逆序对数目就是整个数组需要交换的次数,也就是Ultra-QuickSort所需的swap操作次数。 接下来,我需要考虑具体的实现步骤。例如,在归并排序的合并阶段,如何正确统计逆序对的数量。假设有一个数组arr,分成左半部分和右半部分,分别排序后合并。当左半部分的当前元素大于右半部分的当前元素时,说明存在逆序对,此时逆序对的数量应该是左半部分剩余元素的数量,因为左半部分已经是有序的,所有剩余元素都会比当前右半部分的元素大。 另外,需要考虑递归的终止条件,即当数组长度为1时,逆序对数量为0。然后,在递归返回时,将左右子数组的逆序对数目上合并过程中产生的数目,得到总的数目。 测试一下这个思路是否正确。例如,对于输入序列9 1 0 5 4,正确的输出是交换次数为6。那么用归并排序计算逆序对是否得到6?让我手动计算一下。原始序列是9,1,0,5,4。分解后的逆序对可能有:9与1、0、5、4;1与0;5与4。总共有6个逆序对,确实如此。因此,这个方法是正确的。 接下来,我需要编写代码实现这个算法。用Python的话,可以定义一个递归函数,返回排序后的数组和逆序对的数量。在合并过程中,使用双指针遍历左右两个子数组,比较元素大小,并按顺序合并到临时数组中。每当左指针的元素大于右指针的元素时,就将右指针的元素放入临时数组,并增逆序对的数量,增的数量为左子数组剩余元素的个数。 需要注意的是,归并排序的实现中,临时数组的处理可能会影响空间复杂度,但这里问题规模应该可以接受。此外,Python中处理大数组时需要注意递归深度,不过题目中的例子应该不会有问题。 最后,验证代码的正确性。用给出的测试用例9 1 0 5 4,应该输出6。再比如,对于已经排序的数组,逆序对数目为0。另一个例子,比如3,2,1,应该有3个逆序对,交换次数为3。 总结一下,Ultra-QuickSort的swap次数等于数组中的逆序对数目,而归并排序是一个有效的方法来计算这个数目。因此,实现的关键在于正确修改归并排序以统计逆序对的数量。</think>### Ultra-QuickSort算法解析与实现 Ultra-QuickSort的核心是**通过交换相邻元素消除逆序对**,其最小交换次数等于数组中逆序对的总数。例如输入序列$9\ 1\ 0\ 5\ 4$需要6次交换[^1][^3]。 #### 算法原理 1. **逆序对定义**:若$i < j$且$a_i > a_j$,则$(a_i,a_j)$构成逆序对 2. **等价关系**:每次相邻交换最多消除一个逆序对 3. **最优性证明**:最小交换次数=逆序对总数 #### 实现方法(归并排序) ```python def count_inversions(arr): if len(arr) <= 1: return arr, 0 mid = len(arr) // 2 left, inv_left = count_inversions(arr[:mid]) right, inv_right = count_inversions(arr[mid:]) merged = [] inv_count = inv_left + inv_right i = j = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: merged.append(left[i]) i += 1 else: merged.append(right[j]) j += 1 inv_count += len(left) - i merged += left[i:] merged += right[j:] return merged, inv_count # 示例用法 arr = [9, 1, 0, 5, 4] _, swaps = count_inversions(arr) print(f"需要最少交换次数:{swaps}") # 输出6 ``` #### 代码解析 1. **递归分治**:将数组不断二分至单个元素 2. **合并统计**:合并有序子数组时计算跨区间的逆序对 3. **时间复杂度**:$O(n \log n)$,空间复杂度$O(n)$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值