题目大意很简单,给一个序列的数字,只有相邻的数字可以交换位置,最后要使之顺序排列,求总共要进行几次交换。
思路是这样:从1到N个数,只有碰到左边的数比右边的数大时,才需要交换。那么就只要求出每个数,它的右边有多少个比它小的数(即逆序数),然后把求出来的结果加在一起,就是一共需要交换的次数。那又是怎样想到用树状数组呢?
我们知道树状数组可以拿来维护1 - N 这样一个区间的信息,那我们逆向思维一下,既然我们要求一个数的逆序数,也可以反过来,看第 i 个数在 1 到 i-1 这个区间,有多少个比它大,也是一样的。那么新的问题又来了,我们怎么知道,1 到 i-1 有多少个比它大的数,最简单的就是遍历 1到i-1,这办法显然达不到时间要求。那用树状数组可以来优化它,比如,用树状数组来维护比从 1到i-1 比第 i 个数大的数,这样貌似行得通,但事实上,我们无法保证,这个点维护的这个值是否对后面的第 j 个点也有效,因为可能存在 i j 之间的值,因此这种办法也不行。
再逆向思维一下,我们用树状数组(用sum[]表示)来维护从 1到i-1 中比第 i 个数小的值,那么比第 i 个数大的就是 (i-1) - sum[i-1],这里我们可以直接换成 i - sum[i],因为在真正使用的时候,sum[i]会被立即赋值。说到这其实还有个问题没解决:这样的方法依然跟上面提到的记录大的数一样,无法保证对后面的数都有效,怎么办?
那么我们就让这些数按照输入的顺序来查它左边有多少个比它小的。具体实现方法如下:
如测试样例 9 1 0 5 4 ; 9是第一个输入的,我们就先将sum[9]置1,表示该位已存在了,我们只需要统计,此时sum[]数组中从1到9位有多少已经被置1(即表示已经出现则这些数在9的左边)。这时候,问题就彻底变成了树状数组的模型了,每次都会单点更新 i,然后查 1到 i的和。
这题的整体逻辑就是这样,感觉讲得有点乱。。。
但这提到这里还没结束,看看题中a[i]的范围,10位9,超了int范围了,数组开不了这么大,所以我们需要对数据进行离散化,通过这种方法,来使数据更紧凑。
具体方法,直接上代码吧。。。。
说明下,Node中的value记录本身的值,rank用来记录,这个数是第几个输入的,然后,seq[]数组是离散化后的结果,下标表示原先的输入顺序,值表示离散化后的值(其实就是在把所有数顺序排之后,这个数是第几个,然后就用这个序号来表示这个数)
import java.util.Arrays;
import java.util.Scanner;
class Node implements Comparable<Node>{
int value, rank;
public Node(int value, int rank) {
this.value = value;
this.rank = rank;
}
@Override
public int compareTo(Node o) {
return value - o.value;
}
}
public class Main{
static Scanner sc = new Scanner(System.in);
static int n;
static Node [] node;
static int [] seq;
static int [] sum;
static void update(int x) {
while(x <= n) {
sum[x] += 1;
x += x & -x;
}
}
static int sum(int x) {
int ans = 0;
while(x > 0) {
ans += sum[x];
x -= x & -x;
}
return ans;
}
public static void main(String[] args) {
while(true) {
n = sc.nextInt();
if(n == 0) break;
node = new Node[n];
seq = new int[n+1];
sum = new int[n+1];
for(int i = 0; i < n; i++) {
node[i] = new Node(sc.nextInt(), i + 1);
}
Arrays.sort(node);
for(int i = 1; i <= n; i++) {//第 i个大的数字,是第rank个进来的
seq[node[i-1].rank] = i;
}
long ans = 0;
for(int i = 1; i <= n; i++) {
update(seq[i]);
ans += i - sum(seq[i]);
}
System.out.println(ans);
}
System.exit(0);
}
}