经典的求逆序对问题,在之前写过的一篇文章(《关于merge sort》(http://blog.csdn.net/elicococoo/article/details/18090743))中写了如何用merge sort的方法来解这个题,现在记录一下如何用树状数组+离散化求解。
题目:
poj:http://poj.org/problem?id=2299
思路:
1.逆序对的特点就是(升序时)第i个数字前有n个数字大于n,那么在这个数字之前就关于这个数字就有n个逆序对;
2.利用树状数组的思想,先将数组初始化为0,按输入顺序读数更新节点值,每更新一次节点值+1,并且统计这个数之前有多少数小于它,如下:
输入顺序:2 1 4 3
排序顺序:1 2 3 4
树状数组:0 0 0 0
输入2后:
2 1 4 3
1 2 3 4
0 1 0 0
sum += 1 - getsum(2) = 0
输入1后:
2 1 4 3
1 2 3 4
1 1 0 0
sum += 2 - getsum(1)= 1
输入4后:
2 1 4 3
1 2 3 4
1 1 0 1
sum += 3 - getsum(4) = 1输入3后:
2 1 4 3
1 2 3 4
1 1 1 1
sum += 4 - getsum(3) = 2
因此逆序对为2。
3.因为输入的数字过大,数组无力承担,因此要用离散化缩小数组空间。
离散化:用有限的空间表示无限的空间。
保持原来的输入顺序不变,将数字重新赋值并不改变其大小关系,例如输入3 5 2 化为 2 3 1
AC代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define lowbit(x) x & (-x)
const int M = 500000 + 10;
struct Num
{
int pos, v;
bool operator < (const Num &i) const
{
return v < i.v;
}
}nums[M];
int dis[M], n, tree[M];
void add(int a)
{
while(a <= n)
{
tree[a]++;
a += lowbit(a);
}
}
long long sum(int a)
{
long long res = 0;
while(a > 0)
{
res += tree[a];
a -= lowbit(a);
}
return res;
}
main()
{
while(~scanf("%d", &n) && n)
{
memset(nums, 0, sizeof(nums));
memset(dis, 0, sizeof(dis));
memset(tree, 0, sizeof(tree));
for(int i = 1; i <= n; i++)
{
scanf("%d", &nums[i].v);
nums[i].pos = i;
}
sort(nums + 1, nums + n + 1);//以值排序
for(int i = 1; i <= n; i++)//离散化
dis[nums[i].pos] = i;
long long res = 0;
for(int i = 1; i <= n; i++)
{
add(dis[i]);
res += i - sum(dis[i]);
}
printf("%I64d\n", res);
}
}