题目:
在这个问题中,您必须分析特定的排序算法----超快速排序。
该算法通过交换两个相邻的序列元素来处理 n
个不同整数的序列,直到序列按升序排序。
对于输入序列 9 1 0 5 4
,超快速排序生成输出 0 1 4 5 9
。
您的任务是确定超快速排序需要执行多少交换操作才能对给定的输入序列进行排序。
输入格式
输入包括一些测试用例。
每个测试用例的第一行输入整数 n
,代表该用例中输入序列的长度。
接下来 n
行每行输入一个整数 ai,代表用例中输入序列的具体数据,第 i 行的数据代表序列中第 i
个数。
当输入用例中包含的输入序列长度为 0
时,输入终止,该序列无需处理。
输出格式
对于每个需要处理的输入序列,输出一个整数 op
,代表对给定输入序列进行排序所需的最小交换操作数,每个整数占一行。
数据范围
0≤n<500000
,
一个测试点中,所有 n 的和不超过 500000。
0≤ai≤999999999
输入样例:
5
9
1
0
5
4
3
1
2
3
0
输出样例:
6
0
知识背景:
逆序对:对于数列的第 i 个和第 j 个元素,如果满足 i<j 且 a[i]>a[j],则其为一个逆序对;否则不是。
树状数组:是一个查询和修改复杂度都为log(n)的数据结构。主要用于数组的单点修改,区间求和
如果不会树状数组的话可以看看上面这篇博客,写的挺好,讲了树状数组如何处理逆序对问题的。
分析: 题目中是通过不断交换相邻的2个数来达到排序的效果,什么时候需要交换2个数,当 i<j 且 a[i]>a[j]时进行交换就可以了,而这一对要交换的数就构成了逆序对,要交换的次数就等于逆序对的个数。
树状数组的作用是区间维护,单点查询和区间查询,而tree[N]要进行点的更新([x,x]这段区间的维护)和区间查询,也就是tree[1-x]的求和,树状数组刚好能提供这些功能。
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 500010;
//b[N]存储的是a[N]离散化前对应的编号
int b[N],tree[N];
int n;
struct node{
int val;
int pos;
}a[N];
bool cmp(node a, node b){
return a.val < b.val;
}
int lowbit(int x){
return x&(-x);
}
void add(int k, int x){
while(k <= n){
tree[k] += x;
k += lowbit(k);
}
}
int sum(int k){
int res = 0;
while(k){
res += tree[k];
k -= lowbit(k);
}
return res;
}
int main(){
while(cin >> n && n != 0){
//读取位置信息和值
memset(tree, 0, sizeof tree);
for(int i = 1; i <= n; i ++){
cin >> a[i].val;
a[i].pos = i;
}
//这样便可得出数原来应该在的地方
sort(a + 1 ,a + 1 + n, cmp);
//去重
int cnt = 1;
for(int i = 1; i <= n; i ++){
if(i != 1 && a[i].val != a[i - 1].val)
cnt ++;
b[a[i].pos] = cnt;
}
//将每个数前比它小的数相加
long long res = 0;
for(int i = 1; i <= n; i ++){
add(b[i], 1);
res += (i - sum(b[i]));
}
cout << res << endl;;
}
return 0;
}