POJ-2299- Ultra-QuickSort-树状数组离散化

B - Ultra-QuickSort POJ 2299
Time Limit: 7000 MS Memory Limit: 65536 KB

64-bit integer IO format: %I64d , %I64u Java class name: Main

[Submit] [Status]

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

看这数据量,最大数接近十亿,然而时间要求是一秒,普通冒泡肯定是不行了。
事实上可以选用归并排序求逆序对,或者树状数组求逆序对,这里我采用的是树状数组,因为归并排序还没学会。。。
树状数组略解http://blog.csdn.net/qq_32680617/article/details/50867705

我们知道,树状数组的大小就是数据范围的大小。而题中需要排序的数据范围是一到十亿,树状数组显然不可能开十亿那么大,注意到实际最多输入五十万个数,所以我们可以通过数据的离散化,使数据大小和数据编号产生一一对应的关系,这样只需开五十万的数组就能表示十亿的数字。

关于离散化:
当数据只与它们之间的相对大小有关系,而与数据具体大小无关时,可以进行离散化
把无限空间中无限的个体映射到有限的空间中去,以此提高算法的时空效率。

离散化实现
设有4个数:
1234567、123456789、12345678、123456
排序:123456<1234567<12345678<123456789
=> 1 < 2 < 3 < 4
那么这4个数可以表示成:2、4、3、1
这里用相对大小替代了原本很大的数据

本题中利用一个结构体实现数据的离散化

struct Node
{
    int num;//记录数字
    int point;//记录数字对应下标
} node[N]; //接收数据

然后对结构体排序

bool cmp(Node x,Node y)//离散化前需要先排序
{
    return x.num<y.num;//按照数字升序排列
}
sort(node+1,node+n+1,cmp);//排序然后离散化

此时数据和结构体的下标就是一一对应的关系
再将这种关系存储到映射数组map中

for(int i=1; i<=n; i++)
            map[node[i].point]=i;//数据离散化

此时就相当于数据的相对大小都存储在映射数组map中,对于求逆序对数来说,这和存储真实数据大小是没有区别的。

接下来通过树状数组求逆序对数
数据不是很大时,直接将数据插入到树状数组中,数据很大时,离散化之后,将离散化的数据插入到树状数组中(例如本题,就需要离散化后插入)
每插入一个数,就计算它前面比它小的数的个数。
设i为已插入数的个数,那么此时的逆序对就是i-sum(map[i]),其中sum函数在下面的代码中有注释,是用来返回比i小的数的个数。

下面是具体代码实现

#include<stdio.h>
#include<string.h>
#include<string>
#include<stack>
#include<queue>
#include<math.h>
#include<limits.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=500005;
int map[N];//映射数组,用于数据离散化
int c[N];//树状数组
int n;//数字的数量
struct Node
{
    int num;//记录数字
    int point;//记录数字对应下标
} node[N]; //接收数据
bool cmp(Node x,Node y)//离散化前需要先排序
{
    return x.num<y.num;//按照数字升序排列
}
int lowbit(int i)
{
    return i&(-i);//返回i的二进制最后一位1的权值
}
void change(int i)//更新树状数组
{
    //每插入一个数,就计算前面比这个数小的个数,并存在树状数组中
    while(i<=n)
    {
        c[i]=c[i]+1;
        i=i+lowbit(i);
    }
}
int sum(int i)//返回比i小的数的个数
{
    int sum=0;
    while(i>0)
    {
        sum=sum+c[i];
        i=i-lowbit(i);
    }
    return sum;
}
int main()
{
    while(~scanf("%d",&n)&&n)
    {
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&node[i].num);//接收数字
            node[i].point=i;//记录数字对应的下标
        }
        sort(node+1,node+n+1,cmp);//排序然后离散化
        for(int i=1; i<=n; i++)
            map[node[i].point]=i;//数据离散化
        memset(c,0,sizeof(c));
        long long int step=0;//记录最少交换步数
        for(int i=1; i<=n; i++)
        {
            change(map[i]);//插入映射数据
            step=step+i-sum(map[i]);//每次插入都计算插入数据前还有多少比插入数据小的数据
        }
        printf("%lld\n",step);
    }
    return 0;
}

还有一道二维树状数组,晚上再写吧,没时间了。

你可能感兴趣的:(离散化,树状数组)