Description
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
Output
Sample Input
5
9
1
0
5
4
3
1
2
3
0
Sample Output
6
0
大概题意:
给一串长度为N的序列, 问要经过多少次调换才能形成一个升序。
大致思路:
记录元素的大小及坐标, 因为要形成升序,前面大的元素要交换到后面,所以就是转换成了求一段序列的逆序数。
这道题很容易想到冒泡排序法暴力,复杂度O(N^2) ,但这道题看到有求逆序数,优先用树状数组或线段树,如果用树状数组,效率会快很多很多。
之前写过一篇用树状数组求逆序数的方法,可以参考一下:链接
如果用树状数组,那么问题就来了,a[ i ] 的范围【0, 999999999】, 数据范围有点大,而且通过样例可以发现, 数据也不是连续的,所以按数据范围开数组这个太恶心了,我们不妨做一下离散化的处理,之后求逆序数就可以交给树状数组啦。
1、为什么要离散化?
正如前面所说的a[ i ]的范围最大可以去到 999,999,999. 如果给一组的一组数据是 999999999, 1, 0, 5,3 . 就5个数, 但树状数组的范围就要开到【0, 999999999】, 造成了大量的内存浪费,而且题目也不允许,这笔买卖可不划算。所以我们要用把这五个数离散化一下。
2、如何离散化?
记录他们的下标,用下标来搞事情。
既然要同时记录下标和数值,我们可以考虑两种映射
①map, 效率有点低
②定义一个结构体 v用于记录数值(排序用), no用于记录下标。
struct node
{
int v, no;
};
输入序列之后,按升序排序,这样我们就可以得到一个按元素升序排好序表示每个元素出现的顺序的序列。
举个栗子: 999999999, 1, 0, 5, 3
t.v | 9...9 | 1 | 0 | 5 | 3 |
t.no | 1 | 2 | 3 | 4 | 5 |
t.v (排序后) | 0 | 1 | 3 | 5 | 9...9 |
t.no(排序后) | 3 | 2 | 5 | 4 | 1 |
很显然,我们就可以发现排序后的 t.no 序列的逆序数就是需要交换的次数,例如9...9 的 下表为 1 ,它前面 t.no 比它大的数有四个, 说明在原序列(未排序)中 9...9的后面(因为下标也代表出现的顺序,越大越靠后)比 9...9 小(因为排序)的有四个。所以问题到这里就可以交给树状数组去解决啦!
AC code:
///poj 2299 树状数组 离散化 求逆序数
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define INF ox3f3f3f3f
using namespace std; const int MAXN = ; struct node
{
int v;
int no;
}c[MAXN]; int aa[MAXN];
int N; bool cmp(struct node a, struct node b)
{
return a.v < b.v;
} int lowbit(int i)
{
return i&(-i);
} void add(int i, int value)
{
while(i <= N)
{
aa[i]+=value;
i+=lowbit(i);
}
} int sum(int i)
{
int res = ;
while(i > )
{
res+=aa[i];
i-=lowbit(i);
}
return res;
} int main()
{
while(scanf("%d", &N) != EOF && N)
{
memset(aa, , sizeof(aa));
for(int i = ; i <= N; i++)
{
scanf("%d", &c[i].v);
c[i].no = i;
}
sort(c+, c+N+, cmp);
long long int ans = ;
for(int i = ; i <= N; i++)
{
add(c[i].no, );
ans += i-sum(c[i].no);
}
printf("%lld\n", ans);
}
return ;
}
当然如果这样子你还是没办法理解,那我们可以在进一步转换,通过数组 aa[ ], 还原回原本的序列结合冒泡排序的思想进行求解
t.v | 0 | 1 | 3 | 5 | 9...9 |
t.no | 3 | 2 | 5 | 4 | 1 |
aa[ t.no ] | 1 | 2 | 3 | 4 | 5 |
aa[1] ~ aa[5] | 5 | 2 | 1 | 4 | 3 |
很明显,这里的aa[ i ] 表示的是第 i 个元素在整个序列中是第 aa[ i ] 大。那么结合冒泡排序的思想,这里的逆序数表示的是当前元素转到指定位置需要swap多少次
i | t[1] | t[2] | t[3] | t[4] | t[5] | i - sum( aa[i] ) |
1 | 0 | 0 | 0 | 0 | 1 | 1 - 1 = 0 |
2 | 0 | 1 | 0 | 0 | 1 | 2 - 1 = 1 |
3 | 1 | 1 | 0 | 0 | 1 | 3 - 1 = 2 |
4 | 1 | 1 | 0 | 1 | 1 | 4 - 3 = 1 |
5 | 1 | 1 | 1 | 1 | 1 | 5 - 3 = 2 |
例如, 按照冒泡排序法,第二个数 1 要跟 9... 9 交换位置一次, 序列变成 1,9...9, 0, 5, 3
第三个数 0 要跟前面两个数交换位置,所以交换次数为2, 序列变成 0,1,9...9,5,3
第四个数 5 要跟前面的 9...9 交换位置,交换次数为1,序列 0,1,5,9...9, 3
第五个数 3 要跟前面两个数交换位置,交换位置为2, 序列 0,1,3,5,9...9
答案就是:0+1+2+1+2 = 6;
AC code:
///poj 2299 树状数组 离散化 求逆序数
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define INF ox3f3f3f3f
using namespace std; const int MAXN = ; struct node
{
int v;
int no;
}c[MAXN]; int aa[MAXN];
int t[MAXN];
int N; bool cmp(struct node a, struct node b)
{
return a.v < b.v;
} int lowbit(int i)
{
return i&(-i);
} void add(int i, int value)
{
while(i <= N)
{
t[i]+=value;
i+=lowbit(i);
}
} int sum(int i)
{
int res = ;
while(i > )
{
res+=t[i];
i-=lowbit(i);
}
return res;
} int main()
{
while(scanf("%d", &N) != EOF && N)
{
memset(t, , sizeof(t));
for(int i = ; i <= N; i++)
{
scanf("%d", &c[i].v);
c[i].no = i;
}
sort(c+, c+N+, cmp); for(int i = ; i <= N; i++)
aa[c[i].no] = i; long long int ans = ;
for(int i = ; i <= N; i++)
{
add(aa[i], );
ans += i-sum(aa[i]);
}
printf("%lld\n", ans);
}
return ;
}