树状数组 离散化 求逆序数POJ 2299Ultra-QuickSort解题报告

来源:互联网 发布:红旗linux系统 编辑:程序博客网 时间:2024/05/20 05:30

Ultra-QuickSort
Time Limit: 7000MS Memory Limit: 65536K
Total Submissions: 49181 Accepted: 17995
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
Source
Waterloo local 2005.02.05
本题即求逆序数,证明如下:
假设: 总的交换次数就是逆序对数总数。等价假设:一次交换次数等于一个逆序对。
设两个数x1,x2,使x1大于x2,且x1与x2之间有N(大于等于0)个数,…x1…x2…。(x1,x2)是一个逆序对。其它逆序对之间交换经过若干步之后,一定能使x1,x2相邻:…x1x2…(*见证明2)。下面x1、x2交换顺序,交换次数增加一次。此后x1、x2不再交换。所以,一对逆序只产生一次交换,且根据题意,仅有逆序对之间产生交换。
其它逆序对同理。
综上,总的交换次数就是逆序对总数。
/*
证明2:当N>=1时,令x1与x2之间的数为xn:…x1…xn…x2…
情况1、xn>x1。则必有xn>x2。xn会不断向后交换离开x1与x2之间。
情况2、xn小于x2。则xn必有xn小于x1。xn会向下交换离开x1与x2之间。
情况3、xn小于x1&&xn小于x2。则x1会向后与xn交换使xn离开x1与x2之间。
情况4、xn小于x1&&xn>x2。与x1>x2矛盾,不可能出现。
综上,x1、x2之间任意一个数都会使x1与x2之间的距离变小,最终使得x1与x2相邻,即距离减小到0。
*/
如果下标从0开始计算,那么对于数列中的第i个数,它前面一共有i个数字。也就是这个数字能产生逆序对的最大个数是i,此时如果能找到它前面的数字中,有k个比它小,也就是前面有k个数字不产生逆序对。那么第i个数字对逆序对的贡献就是i-k。此时我们就将问题转化成,求前i个数中有多少个数比第i个数小。可以在输入过程中对每个数的逆序数求解,建一个vis数组(初始化为0),只要输入一个数,在它的位置标记为1,然后计算出它的左边一共有多少数被标记了就可以知道多少个数比他小了,当然逆序数也就知道了,求从左到右数的和,这是树状数组最擅长的了。
但是每个数的范围是0 ≤ a[i] ≤ 999,999,999,我们不可能开那么大的vis数组,即使开的了也会浪费很多,这个时候我们就要对数据进行离散化处理了。所谓离散化,我们的了解就是对原数组按值排序,然后按大小顺序用下标代替原数,这样我们就可以把数据范围缩小到1-500000。
//用归并排序也行,只是慢一点。http://blog.csdn.net/dafang_xu/article/details/48182069

#include <stdio.h>#include <algorithm>#include <cstring>#define maxn  500005using namespace std;int tree[maxn],n;struct number{    int value, b, pos;//b是value的大小顺序,用以代替范围过大的value,达到离散化的目的,pos是value的输入顺序}num[maxn];  //这个结构体是为离散化创建的,int lowbit(int x) {    return x&(-x);}void Insert(int x)//将x插入,每个最大子树都要更新{    while (x <= n){        tree[x] += 1;//最大子树更新        x += lowbit(x);//枚举每个最大子树    }}int getSum(int x)//返回比x小的数的总数,即求前x的和。加上之前的最大子树即可。{    int s = 0;    while (x){        s += tree[x];//加上最大子树        x -= lowbit(x);//枚举前面的每一个最大子树    }    return s;}int cmp(number x, number y){    return x.value < y.value;}int cmp2(number x, number y){        return x.pos < y.pos;}int main(){    freopen("output.txt","w",stdout);    freopen("input.txt","r",stdin);    while (scanf("%d", &n) && n)    {        for (int i = 1; i <= n; i++){            scanf("%d",&num[i].value);            num[i].pos = i;        }        sort(num+1, num+1+n, cmp);        for (int i = 1; i <= n; i++) num[i].b = i;        sort(num+1, num+1+n, cmp2);  //两次的排序就是离散化的过程        memset(tree, 0, sizeof(tree));        __int64 sum = 0;//5万的完全逆序数列,逆序数超过int        for (int i = 1; i <= n; i++){            sum += (__int64)(i - 1 - getSum(num[i].b));            Insert(num[i].b);        }        printf("%I64d\n",sum);    }    return 0;}
0 0
原创粉丝点击