题目大意:输入一个数组序列,要求整个序列进行冒泡排序时,每个元素需要交换位置的总次数。
输入:
输入包含几个测试用例。每个测试用例均以包含单个整数n <500,000(输入序列的长度)的一行开头。接下来的n行中的每行都包含一个整数,即0≤a [i]≤999,999,999,即第i个输入序列元素。输入由长度为n = 0的序列终止。不得处理此序列。
输出:
对于每个输入序列,程序将打印一行包含整数op的单行,op是对给定输入序列进行冒泡排序所需的最小交换操作数。
思路:
输入的数据量在5e5之内, 因此数据量不成问题,而问题在于数值太大99亿,如果直接处理时间复杂度将会被提高很多(因为树状数组使用lowbit进行操作),所以需要对数据进行离散化。
所谓离散化(大佬跳过),在本体中可以理解为,不改变各个值相对的大小,对他们重新赋一组值。
举个例子:30 20 10 40 50 60 这一组数据经过排序后为 10 20 30 40 50 60
如果我们将其值更改为 3 2 1 4 5 6 排序后为 1 2 3 4 5 6
以上操作,使得各个输入位置被重新排了序(排序只看各个值得相对大小),并且我们可以知道他们的排序交换次数是一样的,因此我们可以将第一组数据离散化为第二组数据,这样可以大大优化时间复杂度。
将输入数据采用结构体保存,主要保存val(值) 和 id(下标序号),这是离散化一定要做得一部,并且这种思想也在很多地方都用到了,因此需要记下来。保存下标主要是为了在对值进行排序时,记录其之前的位置,根据其之前的位置才可以对其离散化为新的值,详细见代码中。
再说本题求解的思路,因为是求冒泡排序中共交换多少次(假定是按升序),换个角度考虑问题,我们发现只要我们知道整个序列中共有多少个逆序对,我们就知道需要交换多少次了,因为每一对逆序对我们就要交换一次。
而去求这个序列的逆序对,我们使用树状数组保存每一个值之前有多少个比他小的数。又因为是升序,我们需要从后到前检查每一个数有多少个数比他更小,检查时我们按照离散化之后的值,再数轴上维护树状数组,这样getNum得到的每个值就是比x小的数的个数,从后向前每求出一个数,就将该数加1放入树状数组(这是一种很重要的思想),然后下一次加入的时候,就能保证答案的正确性。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 5e5 + 5;
typedef long long ll;
ll a[maxn], C[maxn << 1];
//0≤a [i]≤999,999,999 数据太大要需要进行离散化
int n;
struct N{
ll v;
int id;
bool operator < (const N & w)const{
return v < w.v;
}
}no[maxn];
void update(int x, int v){
for(int i = x;i <= n;i += i & (-i)){
C[i] += v;
}
}
int getNum(int x){
int ans = 0;
for(int i = x;i > 0;i -= i & (-i)){
ans += C[i];
}
return ans;
}
int main(){
while(scanf("%d",&n), n){
memset(C, 0, sizeof C);
for(int i = 1;i <= n;i++){
scanf("%lld", &no[i].v);
no[i].id = i;
}
sort(no + 1, no + n + 1);
//更新a数组
for(int i = 1;i <= n;i++) {
a[no[i].id] = i;
}
ll ans = 0;
for(int i = n; i >= 1;i--){
ans += getNum(a[i] - 1);
update(a[i], 1);
}
printf("%lld\n",ans);
}
return 0;
}