Ultra-QuickSort OpenJ_Bailian - 2299 逆序对 + 树状数组 + 离散化(通过结构体重新排序实现)

题目大意:输入一个数组序列,要求整个序列进行冒泡排序时,每个元素需要交换位置的总次数。

输入:

输入包含几个测试用例。每个测试用例均以包含单个整数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;
}

猜你喜欢

转载自blog.csdn.net/qq_40596572/article/details/104007691