POJ 2299 Ultra-QuickSort (树状数组)

题目链接

题目大意很简单,给一个序列的数字,只有相邻的数字可以交换位置,最后要使之顺序排列,求总共要进行几次交换。

思路是这样:从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);
	}
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值