POJ2299 原题链接:http://poj.org/problem?id=2299
Time Limit: 7000MS | Memory Limit: 65536K | |
Total Submissions: 63588 | Accepted: 23705 |
Description
Ultra-QuickSort produces the output
Your task is to determine how many swap operations Ultra-QuickSort needs to perform in order to sort a given input sequence.
Input
Output
Sample Input
5 9 1 0 5 4 3 1 2 3 0
Sample Output
6 0
原题大意:输入一串数据,求它的逆序数;
思路:暴力的方法肯定超时,用树状数组可以将复杂度降到O(nlogn);
这道题大体的思路为:
1.开一个能大小为这些数的最大值的树状数组,并全部置0;
2.从头到尾读入这些数,每读入一个数就更新树状数组,查看它前面比它小的已出现过的有多少个数sum;
3.用当前位置减去该sum,就可以得到当前数导致的逆序对数了。把所有的加起来就是总的逆序对数;
但是,题目给的一个数的范围最大为999,999,999显然我们的树状数组不能开到那么大;这里要用到离散化,就是把输入的所有数离散化到1~n的范围内在进行1~3步的操作;
离散化过程:
1.创建一个结构体
const int N=5e5+10;
typedef struct{
int val; //输入值
int pos; //原下标
}NODE;
NODE node[N];
在创建一个数组a[N];
把node[N]按照val的值从小到大排列之后令 a[node[i].pos]=i (i=1,2,3,4,5,6...n)
过程如下:
1.拿题目第一个样例,输入的为 val: 9 1 0 5 4;
其原下标为pos: 1 2 3 4 5;
node[i]的 i: 1 2 3 4 5;
2.排序之后 val: 0 1 4 5 9
pos: 3 9 5 4 1
i: 1 2 3 4 5
3.执行a[node[i].pos]=i (i=1,2,3,4,5,6...n)后:
i: 1 2 3 4 5;
a[i]: 5 2 1 4 3;
pos[i].val: 9 1 0 5 4;
如上所示,这样做就将1~9缩小为了1~5且大小关系不变的存储在了a[i]中;这时我们可以忽视掉原数组了,因为大小关系不变的话逆序数的对数也不会改变;
那么问题来了,这道题该如何使用树状数组求解呢?
由于a[i]中储存的数是按照原输入的大小关系存储的,那么我们可以:.从头到尾读入这些数,每读入一个数就更新树状数组,查看它前面比它小的已出现过的有多少个数sum;
还是拿样例:
- 如果数据不是很大, 可以一个个插入到树状数组中,
- 每插入一个数, 统计比他小的数的个数,
- 对应的逆序为 i- sum( a[i] ),
- 其中 i 为当前已经插入的数的个数,
- sum( a[i] )为比 a[i] 小的数的个数,
- i- sum( a[i] ) 即比 a[i] 大的个数, 即逆序的个数
- 但如果数据比较大,就必须采用离散化方法
- 假设输入的数组是9 1 0 5 4, 离散后的结果a[] = {5,2,1,4,3};
- 在离散结果中间结果的基础上,那么其计算逆序数的过程是这么一个过程。
- 1.输入5, 调用add(5, 1),把第5位设置为1
- 1 2 3 4 5
- 0 0 0 0 1
- 计算1-5上比5小的数字存在么? 这里用到了树状数组的sum(5) = 1操作,
- 现在用输入的下标1 -sum(5) = 0 就可以得到对于5的逆序数为0。
- 2. 输入2, 调用add(2, 1),把第2位设置为1
- 1 2 3 4 5
- 0 1 0 0 1
- 计算1-2上比2小的数字存在么? 这里用到了树状数组的sum(2) = 1操作,
- 现在用输入的下标2 - sum(2) = 1 就可以得到对于2的逆序数为1。
- 3. 输入1, 调用add(1, 1),把第1位设置为1
- 1 2 3 4 5
- 1 1 0 0 1
- 计算1-1上比1小的数字存在么? 这里用到了树状数组的sum(1) = 1操作,
- 现在用输入的下标 3 -sum(1) = 2 就可以得到对于1的逆序数为2。
- 4. 输入4, 调用add(4, 1),把第5位设置为1
- 1 2 3 4 5
- 1 1 0 1 1
- 计算1-4上比4小的数字存在么? 这里用到了树状数组的sum(4) = 3操作,
- 现在用输入的下标4 - sum(4) = 1 就可以得到对于4的逆序数为1。
- 5. 输入3, 调用add(3, 1),把第3位设置为1
- 1 2 3 4 5
- 1 1 1 1 1
- 计算1-3上比3小的数字存在么? 这里用到了树状数组的sum(3) = 3操作,
- 现在用输入的下标5 - sum(3) = 2 就可以得到对于3的逆序数为2。
- 6. 0+1+2+1+2 = 6 这就是最后的逆序数
- 分析一下时间复杂度,首先用到快速排序,时间复杂度为O(NlogN),
- 后面是循环插入每一个数字,每次插入一个数字,分别调用一次add()和sum()
- 外循环N, add()和sum()时间O(logN) => 时间复杂度还是O(NlogN)
以上摘自:http://blog.csdn.net/guhaiteng/article/details/52138756(自己还是太菜,无法用自己的语言描述出来
以下为我的ac代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=5e5+10;
typedef struct{
int val;
int pos;
}NODE;
NODE node[N];
int n;
int a[N];
int tree[N];
bool cmp(const NODE& a, const NODE& b){
return a.val<b.val;
}
void add(int i)
{
while(i<=n)
{
tree[i]+=1;
i=i+(i&-i);
}
}
int sum(int i)
{
int s=0;
while(i>0)
{
s=s+tree[i];
i=i-(i&-i);
}
return s;
}
int main()
{
while((scanf("%d",&n))!=EOF)
{
if(n==0) break;
for(int i=1;i<=n;i++)
{
scanf("%d",&node[i].val);
node[i].pos=i;
}
sort(node+1,node+n+1,cmp);//排序
for(int i=1;i<=n;i++)
a[node[i].pos]=i; //离散化
for(int i=1;i<=n;i++)
tree[i]=0; //初始化树状数组
long long ans=0;
for(int i=1;i<=n;i++)
{
add(a[i]);
ans+=i-sum(a[i]);
}
cout<<ans<<endl;
}
}