NYOJ-117 求逆序数(树状数组或归并排序)

来源:互联网 发布:统计网络直报系统 编辑:程序博客网 时间:2024/05/20 18:02

求逆序数

时间限制:2000 ms  |  内存限制:65535 KB
难度:5
描述

在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。

现在,给你一个N个元素的序列,请你判断出它的逆序数是多少。

比如 1 3 2 的逆序数就是1。

输入
第一行输入一个整数T表示测试数据的组数(1<=T<=5)
每组测试数据的每一行是一个整数N表示数列中共有N个元素(2〈=N〈=1000000)
随后的一行共有N个整数Ai(0<=Ai<1000000000),表示数列中的所有元素。

数据保证在多组测试数据中,多于10万个数的测试数据最多只有一组。
输出
输出该数列的逆序数
样例输入
221 131 3 2
样例输出
01

在归并排序的过程中,顺便统计逆序数。第20行即做的这个工作。在14行用的小于等于,这样可以防止相等情况下的误判。由于只有一次10万以上的数据,所以申请10万的固定空间,只动态申请一次大于10万的数据。

01.#include <stdio.h>
02. 
03.#define MAXN 100010
04. 
05.int num[MAXN],T,N;
06.long long sum=0;
07. 
08.void Merge(int *arr,int left,int rightPos,int right,int *tmp)
09.{
10.int leftPos=left,leftEnd=rightPos-1,rightEnd=right;
11.int tmpPos=left;
12.while(leftPos<=leftEnd&&rightPos<=rightEnd)
13.{
14.if(arr[leftPos]<=arr[rightPos])
15.{
16.tmp[tmpPos++]=arr[leftPos++];
17.}
18.else
19.{
20.sum+=leftEnd-leftPos+1;
21.tmp[tmpPos++]=arr[rightPos++];
22.}
23.}
24.while(leftPos<=leftEnd)
25.tmp[tmpPos++]=arr[leftPos++];
26.while(rightPos<=rightEnd)
27.tmp[tmpPos++]=arr[rightPos++];
28.for(int i=left;i<tmpPos;i++)
29.{
30.arr[i]=tmp[i];
31.}
32.}
33. 
34.void MergeSort(int *arr,int left,int right,int *tmp)
35.{
36.if(left==right)
37.return;
38.int mid=(left+right)/2;
39.MergeSort(arr,left,mid,tmp);
40.MergeSort(arr,mid+1,right,tmp);
41.Merge(arr,left,mid+1,right,tmp);
42.}
43. 
44.int main()
45.{
46.scanf("%d",&T);
47.while(T--)
48.{
49.scanf("%d",&N);
50.sum=0;
51.int *tmp=new int[N+5];
52.if(N>100000)
53.{
54.int *num2=new int[N+5];
55.for(int i=0;i<N;i++)
56.scanf("%d",&num2[i]);
57.MergeSort(num2,0,N-1,tmp);
58.}
59.else
60.{
61.for(int i=0;i<N;i++)
62.scanf("%d",&num[i]);
63.MergeSort(num,0,N-1,tmp);
64.}
65.delete tmp;
66.tmp=0;
67.printf("%lld\n",sum);
68.}
69.return 0;
70.}


树状数组(摘之逆序数的几种求法)

以如下序列为例

3  5  4  8  2  6  9

大体思路为:新建一个数组,将数组中每个元素置0

0  0  0  0  0  0  0

取数列中最大的元素,将该元素所在位置置1

0  0  0  0  0  0  1

统计该位置前放置元素的个数,为0

接着放第二大元素8,将第四个位置置1

0  0  0  1  0  0  1

统计该位置前放置元素的个数,为0

继续放第三大元素6,将第六个位置置1

0  0  0  1  0  1  1

统计该位置前放置元素的个数,为1

这样直到把最小元素放完,累加每次放元素是该元素前边已放元素的个数,这样就算出总的逆序数来了

在统计和计算每次放某个元素时,该元素前边已放元素的个数时如果一个一个地数,那么一趟复杂度为O(n),总共操作n趟,复杂度为O(n^2),和第一种方法的复杂度一样了,那我们为什么还用这么复杂的方法

当然,在每次统计的过程中用树状数组可以把每一趟计数个数的复杂度降为O(logn),这样整个复杂度就变为O(nlogn)。

将数据按其值大小从大到小排列,然后将序列中的每个数的下标插入到树状数组中,给当前插入节点及其父节点的个数加1,然后统计该节点前面的数。

当两个数相等时,将下标大的排在前面,这样可以防止相等情况下的误判,即第16到22行的代码。

01.#include <stdio.h>
02.#include <algorithm>
03.#include <string.h>
04.using namespace std;
05.#define MAXN 1000010
06. 
07.int T,N,tree[MAXN];
08.long long sum=0;
09. 
10.struct node
11.{
12.int data;
13.int index;
14.}num[MAXN];
15. 
16.bool compare(node a,node b)
17.{
18.if(a.data!=b.data)
19.return a.data>b.data;
20.else
21.return a.index>b.index;
22.}
23. 
24.int LowBit(int x)
25.{
26.return x&-x;
27.}
28. 
29.void Add(int x)
30.{
31.while(x<=N)
32.{
33.tree[x]++;
34.x+=LowBit(x);
35.}
36.}
37. 
38.long long GetSum(int x)
39.{
40.long long tmp=0;
41.while(x)
42.{
43.tmp+=tree[x];
44.x-=LowBit(x);
45.}
46.return tmp;
47.}
48. 
49.int main()
50.{
51.scanf("%d",&T);
52.while(T--)
53.{
54.scanf("%d",&N);
55.sum=0;
56.for(int i=0;i<N;i++)
57.{
58.scanf("%d",&num[i].data);
59.num[i].index=i+1;
60.}
61.sort(num,num+N,compare);
62.memset(tree,0,sizeof(tree));
63.for(int i=0;i<N;i++)
64.{
65.sum+=GetSum(num[i].index);
66.Add(num[i].index);
67.}
68.printf("%lld\n",sum);
69.}
70.return 0;
71.}



0 0