基于树状数组的逆序数计算方法_周娟

合集下载

树状数组

树状数组

第二题:火柴排队(贪心+逆序对)对距离公式化简得:∑(ai-bi)^2=∑(ai^2-2aibi+bi^2)=∑ai^2+∑bi^2-2∑aibi要求∑(ai-bi)^2最小,就只需要∑aibi最大即可。

这里有个贪心,当a1<a2<…<an,b1<b2<..<bn时,∑aibi最大。

证明如下:若存在a>b,c>d,且ac+bd<ad+bc,则a(c-d)<b(c-d),则a<b,与a>b 矛盾,所以若a>b,c>d,则ac+bd>ad+bc将此式子进行推广:当a1<a2<a3<...<an ,b1<b2<...<bn的情况下∑aibi最大,即∑(ai-bi)^2最小。

然后,将两个序列分别排序,确定每对数的对应关系,明显,同时移动两个序列中的数等效于只移动一个序列中的数,那么,我们就保持一个序列不动,然后根据另外那个序列中的数对应的数的位置,重新定义一个数组,求逆序对个数,就是答案。

例如:对于数据:42 3 1 43 2 1 4先排序:1 2 3 41 2 3 4保持序列1不动,那么序列2中的3就对应序列1中的2位置,2就对应序列1中的1位置,1就对应序列1中的3位置,4就对应序列1中的4位置,那么重定义数组为:2 13 400这个序列的逆序对为(2,1),所以答案是1。

求逆序对方法:1.归并排序2.把数组扫一遍,顺序把每个数加入BIT或者是线段树等数据结构中,同时查询比这个数大的数有几个,加入答案。

复杂度: O(n log n)代码(C++)(树状数组):#include<cstdio>#include<algorithm>#include<cstring>using namespace std;#define MAXN 100010#define lowbit(x)(((~(x))+1)&x)#define MAX 99999997struct saver{int v,t;};saver a[MAXN],b[MAXN];bool cmp(saver x,saver y){return x.v<y.v;}int n,r[MAXN],ans=0;int t[MAXN];void Add(int x){for(int i=x;i<=n;i+=lowbit(i)) t[i]++;}int Sum(int x){int rec=0;for(;x;x-=lowbit(x)) rec+=t[x];return rec;}int main(){scanf("%d",&n);for(int i=0;i++<n;)scanf("%d",&a[i].v),a[i].t=i;for(int i=0;i++<n;)scanf("%d",&b[i].v),b[i].t=i;sort(a+1,a+n+1,cmp),sort(b+1,b+n+1,cmp);for(int i=0;i++<n;) r[a[i].t]=b[i].t;for(int i=n;i;i--) ans+=Sum(r[i]),Add(r[i]),ans%=MAX;printf("%d\n",ans);return 0;}2.这道题明显可以用类似最长上升子序列的动态规划求解,易得思路如下:用f(i,0)表示以i为结尾的且最后一段上升的子序列最大长度,f(i,1)表示表示以i为结尾的且最后一段下降的子序列最大长度,那么答案明显就是max{f(i,0),f(i,1)} 方程:f(i,0)=max{f(j,1)}+1 0<=j<i且h[j]<h[i]f(i,1)=max{f(j,0)}+1 0<=j<i且h[j]>h[i]边界:f(0,0)=f(0,1)=0如果直接DP毫无疑问复杂度是O(n^2),会TLE,但是,考虑到我们每次取最值时候取得都是一个区间里的数,如f(i,0)=max{f(j,1)}+1 0<=j<i且h[j]<h[i]取得就是区间[0,h[i]-1]里的最值,所以可以使用线段树或者是BIT(树状数组)来优化,这样复杂度就是O(n log n),可以过全部数据。

计算逆序数的三种方法

计算逆序数的三种方法

计算逆序数的三种方法
计算逆序数的三种方法是:归并排序法、树状数组法和树状数组优化法。

1. 归并排序法:使用归并排序的思想,将数组分成两部分进行排序,并在合并过程中计算逆序数。

具体步骤为:将数组从中间分成两部分,分别对左右两部分进行递归排序,然后将排好序的左右两部分进行合并,并在合并的过程中计算逆序数。

2. 树状数组法:通过使用树状数组来统计逆序数。

具体步骤为:首先对原数组进行离散化处理,即将每个数字映射到一个连续的整数上。

然后,使用树状数组来记录每个数字在排序后数组中的位置。

从右往左遍历原数组,对每个数字进行更新和查询操作,更新其在树状数组中的位置,查询其左侧比其大的数字个数,并将结果累加,最终得到逆序数。

3. 树状数组优化法:在树状数组法的基础上进行了优化。

该方法利用了基于树状数组的二维前缀和数组,通过将所有未处理的数字插入到树状数组中,同时查询和更新前缀和数组,得到逆序数的计算结果。

该方法的时间复杂度为O(nlogm),其中n 为数组大小,m为离散化后的数组大小。

-逆序解法与顺序解法培训课件

-逆序解法与顺序解法培训课件
顺序解法实现示例
在多个数中寻找和为特定值的所有组合。
示例问题描述
问题背景
一个整数数组 nums 和一个目标值 target。
输入数据
找出 nums 中所有和为 target 的组合,每个组合中元素不可重复。
问题要求
解题思路:逐一枚举数组中的每个元素作为组合的第一个元素,再递归地寻找下一个元素,直到找到所有符合条件的组合。
逆序解法概述
逆序解法是一种针对密码锁的攻击方式,通过已知的密码子串和未知的明文子串,利用逆序运算的方式,推导出明文或密码的一种技术。
逆序解法通常适用于加密算法中,通过对加密算法的特定性质进行分析,寻找可能的逆序排列,从而提高加密算法的安全性。
逆序解法定义
逆序解法在密码学领域有着广泛的应用,如破解加密算法、密码破解、数据恢复等场景中。
01
02
03
04
05
代码实现简单易懂,运行时间较长,可能在处理大规模数据时存在性能问题。
可以进一步优化算法,如使用深度优先搜索(DFS)和剪枝等技巧,以提高运行效率。
示例问题结果分析
07
选择逆序解法还是顺序解法
问题的复杂性和规模
问题的特殊性质
团队的技能和经验
选择考虑因素
尝试多种方法
可以同时尝试逆序解法和顺序解法,以便更好地了解它们的优缺点。
对比分析
可以通过对比分析来评估两种方法的优劣,从而选择最合适的方法。
案例研究
通过案例研究来了解逆序解法和顺序解法的实际应用效果。
选择实践方法
逆序解法和顺序解法是两种不同的解决问题的方法,每种方法都有其适用的问题和优点。在选择使用哪种方法时,需要考虑问题的复杂性和规模、特殊性质以及团队的技能和经验等因素。同时,应该勇于尝试多种方法并进行对比分析,以便更好地了解它们的优缺点。最终的目标是选择最适合问题的方法,并尽可能地提高解决问题的效率和准确性。

求解排列的逆序数

求解排列的逆序数

求解排列的逆序数排列是数学中的一个重要概念,指一组数的有序排列。

在排列中,如果一个数在它之前的位置比它在它后面的位置靠前,则这两个数构成了一个逆序对。

而排列的逆序数,则是指排列中逆序对的个数。

在计算机科学中,求解排列的逆序数是一个常见的问题。

它在排序算法中起着重要的作用,并且有广泛的应用。

接下来,我们将介绍一种常用的算法,用于求解排列的逆序数。

算法描述:给定一个排列P,其中包含n个不同的元素。

我们的目标是求解P 的逆序数。

1. 初始化逆序数count为0。

2. 从左到右遍历排列P的每个元素Pi。

3. 对于每个元素Pi,向右遍历它之后的元素Pj。

4. 如果Pi > Pj,则逆序数count加1。

5. 返回逆序数count作为结果。

下面,我们将对算法进行详细的说明,并通过示例进行演示。

示例:假设我们有一个排列P,包含5个不同的元素。

下面是P的排列:P = [3, 1, 4, 5, 2]我们将按照上述算法求解P的逆序数。

首先,初始化逆序数count为0。

然后,我们从左到右遍历P的每个元素。

对于元素3,向右遍历它之后的元素。

比3小的元素有1和2,因此逆序数count加2。

对于元素1,向右遍历它之后的元素。

比1小的元素有0个,因此逆序数count保持不变。

对于元素4,向右遍历它之后的元素。

比4小的元素有3个,因此逆序数count加3。

对于元素5,向右遍历它之后的元素。

比5小的元素有0个,因此逆序数count保持不变。

对于元素2,向右遍历它之后的元素。

比2小的元素有1个,因此逆序数count加1。

最终,逆序数count的值为6,即P的逆序数为6。

通过以上示例,我们可以看出,求解排列的逆序数的算法是相对简单且高效的。

它可以帮助我们更好地理解排列中元素的相对顺序,并在排序算法中发挥重要作用。

总结:本文介绍了求解排列的逆序数的算法。

通过该算法,我们能够准确计算排列中逆序对的个数。

这对于排序算法的实现以及其他相关领域的问题有着广泛的应用。

逆序解法与顺序解法培训课件

逆序解法与顺序解法培训课件

计算效率比较
逆序解法
逆序解法的计算效率通常比顺序解法要高。因为逆序解法是逆向思维,可以 跳过一些不必要的计算步骤,从而减少计算量。同时,逆序解法也可以利用 一些数学公式的逆向形式,简化计算过程。
顺序解法
顺序解法的计算效率相对较低。因为顺序解法需要按照问题的顺序逐步分析 ,不能跳过不必要的计算步骤。在一些大规模的计算问题中,顺序解法的计 算量可能会非常大。
逆序解法在土木工程中的应用
在土木工程中,逆序解法可用于结构分析和设计。通过对结构进行逆序分解,可以更好地 理解结构的性能和安全性,为结构设计提供有力支持。
逆序解法在机械工程中的应用
在机械工程中,逆序解法可用于机构的优化设计和动力学分析。通过逆序分解,可以找出 机构中的性能瓶颈,为机构的优化设计提供指导。
05
逆序解法实现方法详解
算法流程及步骤说明
定义初始状态
确定比较规则
构建逆序对
更新状态
重复步骤
根据问题需求,定义初 始状态,通常是一个有 序序列。
根据问题需求,确定比 较规则,例如大小比较 、字典序比较等。
从初始状态开始,按照 比较规则,构建逆序对 ,即顺序相反的两个元 素。
根据比较规则,将逆序 对中的两个元素交换位 置,更新状态。
案例实现
将所选择的案例实现为相应的代码,并对其进行 测试和验证。
案例讲解
对实现的代码进行详细讲解,包括问题建模、参 数分析、解决方案的制定和实现等。
07
工程应用实例分享
逆序解法在工程中的应用案例
逆序解法在电力工程中的应用
在电力工程中,逆序解法被广泛应用于解决电网的稳定性问题。通过逆序分解,可以准确 地找出电网中的潜在问题,为电网的优化设计和运行提供依据。

逆序对问题---求逆序数

逆序对问题---求逆序数

逆序对问题---求逆序数逆序数:在⼀个排列中,如果⼀对数的前后位置与⼤⼩顺序相反, 即前⾯的数⼤于后⾯的数,那么它们就称为⼀个逆序。

⼀个排列中逆序的总数就称为这个排列的逆序数。

逆序数为偶数的排列称为偶排列;逆序数为奇数的排列称为奇排列。

{设 A 为⼀个有 n 个数字的有序集 (n>1),其中所有数字各不相同。

如果存在正整数 i, j 使得 1 ≤ i < j ≤ n ⽽且 A[i] > A[j],则 <A[i], A[j]> 这⼀个有序对称为 A 的⼀个逆序对,也称作逆序。

逆序对的数量称作逆序数。

例如:数组 <2,3,8,6,1> 的逆序对为:<2,1> <3,1> <8,1> <8,6> <6,1> 共5个逆序对。

对于<2,1>:1 ≤ 1 < 5 ≤ 5 ,A[1] > A[5],所以<A[1],A[5]>为⼀个合法的逆序对。

⽬前求逆序对数⽬⽐较普遍的⽅法是利⽤归并排序做到的时间复杂度。

当然,也可以利⽤树状数组、线段树来实现这种基础功能。

复杂度均为。

}求逆序数⽅法:感谢:法⼀:暴⼒ O(n^2)最简单也是最容易想到的⽅法就是,对于数列中的每⼀个数a[i],遍历数列中的数a[j](其中j<i),若a[i]<a[j],则逆序数加1,这样就能统计出该数列的逆序数总和法⼆:归并的思想,利⽤归并排序过程归并排序的复杂度为O(nlogn),当然此⽅法的复杂度也为O(nlogn)有⼀种排序的⽅法是归并排序,归并排序的主要思想是将整个序列分成两部分,分别递归将这两部分排好序之后,再和并为⼀个有序的序列,核⼼代码如下MergeSort(first,last){If(first==last)returnInt med=(first+last)/2;MergeSort(first,med);MergeSort(med+1,last);Merge(first,last);}在合并的过程中是将两个相邻并且有序的序列合并成⼀个有序序列,如以下两个有序序列Seq1:3 4 5Seq2:2 6 8 9合并成⼀个有序序:Seq:2 3 4 5 6 8 9对于序列seq1中的某个数a[i],序列seq2中的某个数a[j],如果a[i]<a[j],没有逆序数,如果a[i]>a[j],那么逆序数为seq1中a[i]后边元素的个数(包括a[i]),即len1-i+1,这样累加每次递归过程的逆序数,在完成整个递归过程之后,最后的累加和就是逆序的总数const int LENGTH=100;int temp[LENGTH]; //额外的辅助数组int count=0;void Merge(int * array,int first,int med,int last){int i=first,j=med+1;int cur=0;while (i<=med&&j<=last){if (array[i]<array[j]){temp[cur++]=array[i++];}else{temp[cur++]=array[j++];<span style="color:#ff0000;">count+=med-i+1</span>; //核⼼代码,逆序数增加}}while (i<=med){temp[cur++]=array[i++];}while (j<=last){temp[cur++]=array[j++];}for (int m=0;m<cur;m++){array[first+m]=temp[m];}}void MergeSort(int *array,int first,int last){if (first==last){return ;}int med=first+(last-first)/2;MergeSort(array,first,med);MergeSort(array,med+1,last);Merge(array,first,med,last);}法三:树状数组树状数组法三:还是以刚才的序列3 54 8 2 6 9⼤体思路为:新建⼀个数组,将数组中每个元素置00 0 0 0 0 0 0取数列中最⼤的元素,将该元素所在位置置10 0 0 0 0 0 1统计该位置前放置元素的个数,为0接着放第⼆⼤元素8,将第四个位置置10 0 0 1 0 0 1统计该位置前放置元素的个数,为0继续放第三⼤元素6,将第六个位置置10 0 0 1 0 1 1统计该位置前放置元素的个数,为1……这样直到把最⼩元素放完,累加每次放元素是该元素前边已放元素的个数,这样就算出总的逆序数来了在统计和计算每次放某个元素时,该元素前边已放元素的个数时如果⼀个⼀个地数,那么⼀趟复杂度为O(n),总共操作n趟,复杂度为O(n^2),和第⼀种⽅法的复杂度⼀样了,那我们为什么还⽤这么复杂的⽅法当然,在每次统计的过程中⽤树状数组可以把每⼀趟计数个数的复杂度降为O(logn),这样整个复杂度就变为O(nlogn)树状数组是⼀种很好的数据结构,这有⼀篇专门描述的⽂章将序列中的每个数按照从⼤到⼩的顺序插⼊到树状数组中,给当前插⼊节点及其⽗节点的个数加1,然后统计该节点下边及右边放置元素的个数HDU1394求逆序数法⼀:数学逆序数性质对于这个题求把第⼀个数放到最后⼀个数的最⼩逆序数,对于原序列⽽⾔,如果把第⼀个数放到最后⼀个数,逆序列增加n-num[0]+1,逆序列减少num[0].如何得到的?#include "iostream"#include "cstdio"using namespace std;#include <stdio.h>int main(){int num[5005],n;while(scanf("%d",&n)!=EOF){for(int i=0;i<n;i++)scanf("%d",&num[i]);int sum=0,temp;for(int i=0;i<n;i++)for(int j=i+1;j<n;j++)if(num[i]>num[j]) sum++;temp=sum;for(int i=n-1;i>=0;i--){temp-=n-1-num[i];temp+=num[i];if(temp<sum)sum=temp;}printf("%d\n",sum);}return0;}法⼆:树状数组解法分析:#include <cstdio>#include <cstring>#include <algorithm>#include <iostream>using namespace std;#define N 5005#define inf 999999999int c[N];int a[N];int lowbit(int x){return x&(-x);}void update(int x){while(x<N){c[x]++;x+=lowbit(x);}}int get_sum(int x){int ans=0;while(x>0){ans+=c[x];x-=lowbit(x);}return ans;}int main(){int n, i, j, k;while(scanf("%d",&n)==1){memset(c,0,sizeof(c));int num=0;for(i=1;i<=n;i++){scanf("%d",&a[i]);a[i]++;num+=get_sum(N-1)-get_sum(a[i]);update(a[i]);} ///以上⽤树状数组求序列a的逆序数int minh=inf;for(i=1;i<=n;i++){num=num-(a[i]-1)+(n-a[i]); ///每次由之前的逆序数推出此时的逆序数 minh=min(minh,num); ///更新最⼩值}printf("%d\n",minh);}}两代码区别之处就在于求逆序数的⽅法。

c++逆序对个数 树状数组

逆序对个数和树状数组是两个不同的概念,但它们在某些算法中可以一起使用。

1. 逆序对个数:逆序对是数组中两个元素,第一个元素大于第二个元素。

在C++中,可以使用STL中的`std::sort`函数来对数组进行排序,然后使用双指针法来计算逆序对的个数。

具体实现如下:```cpp#include <iostream>#include <algorithm>using namespace std;int reversePairs(int* nums, int numsSize) {sort(nums, nums + numsSize);int count = 0, i = 0, j = numsSize - 1;while (i < j) {if (nums[i] > nums[j]) {count += j - i;i++;} else {j--;}}return count;}```2. 树状数组:树状数组是一种可以高效地维护和更新前缀和的数据结构,也称为 Fenwick 树。

在C++中,可以使用一个数组和一个辅助数组来实现树状数组。

具体实现如下:```cpp#include <iostream>#include <vector>using namespace std;class FenwickTree {public:FenwickTree(int n) {tree.resize(n + 1);prefixSum.resize(n + 1);}void update(int index, int delta) {while (index <= tree.size() - 1) {tree[index] += delta;prefixSum[index] += delta;index += index & -index;}}int query(int index) {int sum = 0;while (index > 0) {sum += prefixSum[index];index -= index & -index;}return sum;}private:vector<int> tree; // Fenwick tree arrayvector<int> prefixSum; // prefix sum array for Fenwick tree array}; ```。

POJ3067Japan(树状数组求逆序数)-电脑资料

POJ3067Japan(树状数组求逆序数)-电脑资料Japan Time Limit:1000MS Memory Limit:65536K Total Submissions:22258Accepted:5995DescriptionJapan plans to welcome the ACM ICPC World Finals and a lot of roads must be built for the venue. Japan is tall island with N cities on the East coast and M cities on the West coast (M <= 1000, N <= 1000). K superhighways will be build. Cities on each coast are numbered 1, 2, ... from North to South. Each superhighway is straight line and connects city on the East coast with city of the West coast. The funding for the construction is guaranteed by ACM. A major portion of the sum is determined by the number of crossings between superhighways. At most two superhighways cross at one location. Write a program that calculates the number of the crossings between superhighways.InputThe input file starts with T - the number of test cases. Each test case starts with three numbers – N, M, K. Each of the next K lines contains two numbers – the numbers of cities connected by the superhighway. The first one is the number of the city on the East coast and second one is the number of the city of the West coast.OutputFor each test case write one line on the standard output:Test case (case number): (number of crossings)Sample Input13 4 41 42 33 23 1Sample OutputTest case 1: 5SourceSoutheastern Europe 2006#include<iostream>#include#include<stdio.h>#include<str ing.h>#include<stdlib.h>using namespace std;int n,m,k;__int64c[1000100];struct node{ int x; int y;} q[1000500];bool cmp(node a,node b){ if(a.x==b.x) { return a.y<=b.y; } else { return a.x<b>0) { sum += c[i]; i = i - lowbit(i); } return sum;}int main(){ int T; int p = 0; scanf("%d",&T); while(T--) { scanf("%d%d%d",&n,&m,&k); memset(c,0,sizeof(c)); for(int i=0; i<k; -="" __int64="" ans="0;" case="" d:="" i="0;" int="" pre="" return="" test=""></k;></b.x;></stdlib.h></string.h></stdio.h></iostre am>。

634521逆序数怎么求

634521逆序数怎么求
634521逆序数是12。

逆序数含义:在n个数码1,2,…,n的全排列j1j2…jn中,若一个较大的数码排在一个较小的数码的前面,则称它们构成反序,亦称逆序。

求逆序数时,可以从前到后将相邻的两个数进行比较,求出逆序及逆序数。

逆序数的计算
直接计数
计算一个排列的逆序数的直接方法是逐个枚举逆序,同时统计个数。

例如在序列{2,4,3,1}中,逆序依次为(2,1),(4,3),(4,1),(3,1),因此该序列的逆序数为4。

归并排序
直接计数法虽然简单直观,但是其时间复杂度是O(n^2)。

一个更快(但稍复杂)的计算方法是在归并排序的同时计算逆序数。

逆序问题及其几种解法

逆序问题及其⼏种解法设A为⼀个有n个数字的序列,其中所有的数字各不相同。

如果存在正整数i和j,使得1≤i<j≤n且A[i]>A[j],那么数对(A[i],A[j])就被称为A的⼀个逆序对,也称作逆序,逆序对的数量就是逆序数。

如下图所⽰,(A[2],A[4])就是⼀个逆序对。

分治法假设我们要统计数列A中逆序对的个数。

如图所⽰,我们可以将数列A分成两半得到数列B和数列C。

因此,数列A中所有的逆序对必属于下⾯三者其⼀:(1). i,j都属于数列B的逆序对(2). i,j都属于数列C的逆序对(3). i属于数列B⽽j属于数列C的逆序对所以,我们只需要分别统计这三种逆序对,然后再加起来就⾏了。

(1)和(2)可以通过递归求得,对于(3),我们可以对数列C中的每个数字,统计在数列B中⽐它⼤的数字的个数,再把结果加起来即可。

因为每次分治时数列的长度都会减半,所以递归的深度是O(log n),⽽每⼀层有O(n)个操作,因此算法的时间复杂度为O(n log n)。

class Solution {public:int reversePairs(vector<int>& nums) {int n = nums.size();if (n <= 1) return 0;auto mid = nums.begin() + n / 2;vector<int> left(nums.begin(), mid);vector<int> right(mid, nums.end());int cnt = 0;cnt += reversePairs(left);cnt += reversePairs(right);Processing math: 100%int nums_idx = 0;int left_idx = 0;int right_idx = 0;while (nums_idx < n) {if (left_idx < left.size() && (right_idx == right.size() || left[left_idx] <= right[right_idx])) {nums[nums_idx++] = left[left_idx++];} else {cnt += n / 2 - left_idx;nums[nums_idx++] = right[right_idx++];}}return cnt;}};树状数组我们构建⼀个值的范围是1∼n的树状数组,按照j=0,1,2,⋯,n−1进⾏如下操作:j−sum(A[j])add(A[j],1)对于每个j,树状数组查询得到的前A[j]项的和就是满⾜i<j,A[i]≤A[j]的i的个数。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

28卷第2期2011年4月V ol.28No.2Apr.,2011华东交通大学学报Journal of East China Jiaotong University文章编号:1005-0523(2011)02-0045-05收稿日期:2010-12-30基金项目:国家自然科学基金项目(11061014);江西省自然科学基金项目(2010GZS0031);华东交通大学科学研究项目(09RJ07)作者简介:周娟(1977-),女,讲师,研究方向为算法设计与分析。

基于树状数组的逆序数计算方法周娟1,曹义亲1,谢昕2(华东交通大学1.软件学院;2.信息学院,江西南昌330013)摘要:n 个元素组成的置换a [1],a [2],…,a [n ]。

若i <j 且a [i ]>a [j ],则称(a [i ],a [j ])是一个逆序对。

置换中逆序对的个数称为置换的逆序数。

按定义,计算逆序数要通过n ()n -1/2此次比较,时间复杂度是O (n 2)。

设计了一种新的方法,利用树状数组计算逆序数,时间复杂度降为O (n log 2(n ))。

主要思路是将元素从大到小依次放置在数状数组中,对于每一个元素i 来说,因它前面的数比它大而计算出逆序数t [i ],利用树状数组的结构特征,即可以O (log 2(n ))的时间复杂度而统计出t [i ],那么最终总的逆序数为∑t [i ]。

关键词:树状数组;逆序数;置换;算法;线段树中图分类号:O241.6;TP301.6文献标识码:An 个数的置换a [1],a [2],…,a [n ]也称为排列或数组[1-2],若i <j 且a [i ]>a [j ],则称(a [i ],a [j ])是一个逆序对。

置换中的逆序对的个数称为置换的逆序数[1,3]。

逆序数是奇数的置换叫奇置换,否则叫偶置换。

逆序数一般分成n 个部分进行计算。

令t [i ]表示逆序对(a [j ],i )的个数,即排在i 的左边且比i 大的数的个数,则逆序数为t [1]+t [2]+…+t [n ]。

文[4]中利用轮换和归并排序计算奇偶性。

一般来说,t [i ]的计算方法是:设置1个全0的排列c [1],c [2],…,c [n ],对大于i 的数a [j ],将c [j ]置为1,设i 在位置p ,计算位置p 左边1的个数就是t [i ],然后置c [p ]=1,下一步计算t [i -1]。

例1设n =8,a ={3,2,1,5,8,4,6,7},则t [1]=2,t [2]=1,t [3]=0,t [4]=2,t [5]=0,t [6]=t [7]=1,t [8]=0。

上述方法的时间复杂度是n 2。

本文提出一种复杂度为n log 2n 的计算逆序数的算法,采用树状数组[5]。

1树状数组定义1设i 整除2k ,i 不能整除2k +1,k 为非负整数,则称2k 是i 的最小比特,记为lowbit (i )。

例28的最小比特是8,6的最小比特是2。

把i 化为2进制数后,最后的连续的0的个数就是k 。

奇数的最小比特是1,若i =2k ,则i 的最小比特就是i 。

定义2设c [1],c [2],…,c [n ]是一个数组,c [i ]称为点或数。

定义c [i ]的父节点是c [i +lowbit (i )]。

称c 为树状数组。

性质1若i 是奇数,则c [i ]没有子节点。

以c [i ]为根的子树只有1个点。

性质2若i 的最小比特是2k ,则以c [i ]为根的子树有2k 个点,有k 个子节点,它的k 个子节点是(若i 的二进制数是***100000,以k =5为例):c [***10000],c [***11000],c [***11100],c [***11110],c [***11111]例3c [8]为根的子树有8个点,c [8]的子节点是c [4],c [6],c [7]。

c [4]的子节点是c [2],c [3]。

2011年华东交通大学学报c [6]的子节点是c [5]。

如表1和图1所示。

图1是一颗空树,尚未放入需要计算的项目,依定义2即可得到此父子结构的一颗确定的树,据此结构特征,再通过定义c 的内涵以及具体问题所引入的数组b 的内涵等,即可用来解决具体计算问题。

也就是说,树状数组是一个特定结构的有利工具。

定义3设c [1],c [2],…,c [n ]是树状数组,b [1],b [2],…,b [n ]是与之对应的一个n 元数组。

j =lowbit (i ),定义c [i ]的值为c [i ]=b [i -j +1]+b [i -j +2]+…+b [i ]。

(1)即c [i ]是到i 为止的lowbit (i )个数的和。

例4设n =10,则c [1]=b [1],c [2]=b [1]+b [2],c [3]=b [3],c [4]=b [1]+b [2]+b [3]+b [4]=c [2]+b [3]+b [4],c [5]=b [5],c [6]=b [5]+b [6],c [7]=b [7],c [8]=b [1]+b [2]+b [3]+b [4]+b [5]+b [6]+b [7]+b [8]=c [4]+c [6]+c [7]+b [8],c [9]=b [9],c [10]=b [9]+b [10]。

由例4可以看出c [i ]是b [i ]与c [i ]的子节点的值之和,如图2所示。

算法1计算i 的最小比特2k :int lowbit (int i ){return i&(i^(i -1));//&按位与和^按位异或是c 语言的位运算符[6-7])}算法2计算前m 项和:int Sum (int m ){int sum=0;while (m>0){sum+=c[m];m-=lowbit (m );}return sum ;图1树状数组-空树Fig.1Empty arborescence arrayi 12345678lowbit(i )12141218i 的父节点i +lowbit(i )244868816表1树状数组Tab.1Arborescence array 图2树状数组c 和数组bFig.2Arborescence array c and array b46第2期周娟,等:基于树状数组的逆序数计算方法}由此可知,若m的二进制数中有s个1,则计算前m项的和只要计算s次,即计算t[i]的次数为s( k)小于log2(n)次。

那么总次数小于n log2(n)算法3如果某个数值发生改变,例如b[p]的值增加了num,那么c[p]和c[p]的父节点,以及其父节点的父节点……的值都要增加num:void plus(int p,int num){while(p<=n){c[p]+=num;p=p+lowbit(p)}}2树状数组计算逆序数算法4计算置换a[1],a[2],…,a[n](是1,2,…,n的一个排列)的逆序数Num=t[1]+t[2]+…+t[n]:Num=0;for(i=1;i<=n;i++)at[a[i]]=i;//引入数组at,at[i]表示i在第at[i]个位置for(i=1;i<=n;i++)c[i]=0;for(x=n;x>=1;x--){p=at[x];//把数x放在第p个位置Num+=Sum(p);//位置p左边有多少个数(比x大的数)plus(p,1);//更新c[p]和c[p]的祖先的值}下面以例1来诠析算法4。

1)首先建立一棵空树(树状数组),如图1,这棵树共有8个位置(或称节点),每个位置上将放置数组a 的一个元素,放置完毕如图3;at[a[i]]表示元素a[i]放置在at[a[i]]节点上。

2)定义4b为节点上所放元素的个数,对于图1,b[i]=0,i=1,...,8;对于图2,b[i]=1,i=1, (8)3)定义数组c,c[i]为以节点i为根的树上所含元素的个数,满足式(1)。

4)定义sum(i)为放置在i之前的节点,所含元素的个数和,可以运用算法2。

5)首先放置最大的元素a_max,a_max=max(a[1],…,a[n]),即n;将它放置在题设所放的位置,at[a_max]处,对于最大数的逆序数t[at[a_max]]为0,因为在它之前没有比它大的数,此时位置at[a_max]的左边也是空的,如图4中(a)第1步;再放第2大的数,如果它放在a_max之前,那么它的逆序数也为0,否则为1,如图4中(b)第2步;依次放置,某个数x放置在at[x]处,此时求x的逆序数t[at[x]],即为求在at[x]位置的左下方和下方一个有多少个数已经放置了,即为sum(at[x]);每放置一个元素,就以plus来更新c,计算过程和树状数组的维护过程如表2和图4。

图3树状数组-放入置换后(1,3,2,5,8,4,6,7)依各元素位置放入树中Fig.2Add array to the arborescence array with1,3,2,5,8,4,6,7in turn472011年华东交通大学学报表2例1的计算过程Tab.2Calculation process for example 13总结树状数组是一个查询和修改复杂度都为log (n )的数据结构,并支持随时修改某个元素的值,复杂度也为log (n )。

它可以很高效地进行区间统计。

在思想上类似于线段树[8-10],比线段树节省空间,编程复杂度比线段树低,但适用范围比线段树小。

总之树状数组有着简单易用的特点,容易记忆。

(a )第1步(b )第2步(c )第3步(d )第4步(e )第5步(f )第6步(g )第7步(h )第1步图4例1的树状数组维护过程Fig.4Maintenance process of arborescence array for example 1i ,at [i ]12345678xa [32158467第1步c [i ]00000→10→100→1t [x ]00000000第2步c [i ]00001101→2t [x ]00000000→1第3步c [i ]0000110→12→3t [x ]0000000→11第4步c [i ]0000→11113→4t [x ]00000011第5步c [i ]000111→214→5t [x ]000000→211第6步c [i ]0→10→101→21215→6t [x ]00000211第7步c [i ]11→202→31216→7t [x ]00→1000211第8步c [i ]120→13→41217→8t [x ]010→2002114849第2期周娟,等:基于树状数组的逆序数计算方法参考文献:[1]RICHARD A BRUALDI.组合数学[M].5版.北京:机械工业出版社,2009:54-55.[2]王延明.关于排列逆序数的进一步性质及其应用[J].枣庄学院学报,2007,24(5):12-13.[3]张翠,张英瑞.关于逆序数相同的n级排列个数的讨论[J].洛阳师范学院学报,2010,29(5):24-25.[4]周尚超.置换奇偶性的快速算法[J].华东交通大学学报,2007,24(1):87-89.[5]刘洋.基于纹理合成的图像修复与基于分形的图像分割方法的研究与应用[D].吉林:吉林大学,2010:71-73.[6]谭浩强.C程序设计教程[M].北京:清华大学出版社,2007:33.[7]钱能.C++程序设计教程[M].2版.北京:清华大学出版社,2005:119-122.[8]林盛华.线段树在程序设计中的应用[J].大众科技,2005(4):68-69.[9]李博涵,郝忠孝.近似查询中重叠区域的扫描计算[J],计算机工程,2008,34(13):10-12.[10]CHANG YEIN,WU CHENCHANG,SHEN JUNHONG,et al.Range queries based on a structured segment tree in p2p sys-tems[C]//Proceedings of the201017th IEEE International Conference and Workshops on the Engineering of Comput-er-Based Systems,2010:91-99.Algorithm of Inverse Number Based on Arborescence ArrayZhou Juan1,Cao Yiqin1,Xie Xin2(1.School of Software;2.School of Information Engineering,East China Jiaotong University,Nanchang330013,China)Abstract:Suppose there is a permutation of a[1],a[2],…,a[n]that is constituted by n elements.If i<j and a[i]>a [j],then(a[i],a[j])is called an inverted sequence pair.The number of inverted sequence in a permutation is called inverse number of a permutation.According to the definition,the caculation of inverse number must be compared with n(n-1)/2,and the time complexity is O(n2).A new method is devised to calculate the reverse numble by arborescence array and reduce the time complexity to O(n log2(n)).The principal idea of this method is to place elements into arborescence array in descending order.With regard to each element,since the previ-ous number which is bigger and being worked out at reverse number t[i],when taking advantage of the architec-tural feature of arborescence array,it will come to t[i]by the time complexity of O(log2(n))and finally reach the total reverse number as∑t[i].Key words:arborescence array;inverse number;permutation;algorithm;segment tree。

相关文档
最新文档