逆序对(朴素,树状数组)

【解题思路】

计算过程等同于使用冒泡排序法,将输入的数据序列进行排序的算法,

因此,此问题有另外一个名字:【计算冒泡排序次数】

如果 不考虑数据规模及超时,直接进行排序,每移动一次就累加一次,

最后输出累计的移动次数便是正解。

【方法1:朴素算法】

const int MAXI = 100002;
int cnt[MAXI];
int num[MAXI];
int n,i,j,answer,val;
int main(){
	scanf("%d",&n);
	for(i=1;i<=n;i++){
		scanf("%d",&num[i]);
		cnt[i]=0;
	}
	answer = 0;
	for(i=1;i<=n;i++){
		val = num[i];
		for(j=val+1;j<=n;j++){
			answer += cnt[j];
		}
		cnt[val]++;
	}
	
	printf("%d\n",answer);
	return 0;
}

num数组中存放:输入的数据序列

cnt数组中存放:已经处理的每个数据的出现次数(1。。。n上长顺序),

如:cnt[4]=2 是指4在此前的处理中遇到了几2次。

如果当前比较数字是3,那么需要分析比3大的值 cnt[4]...cnt[max]出现的次数,

累加的 次数之和就是逆序对个数:

for(j=val+1;j<=n;j++){
            answer += cnt[j];
}

【方法2:树状数组算法】

由于以上方法1的算法是N方的复杂度,题中要求N<100000,

显然会超时,大概只能通过50%用例。

优化方法:计算cnt[i]...cnt[max]出现的次数的算法复杂度从方法1的N次,降低为Log N次

使用数据结构:树状数组 BIT(Binary Indexed Tree)

1、从BIT结构中查询 :每次减去二进制的最后一个1位,直到二进制位为0;

 公式:i -= (i & -i)

int get(int i){
    int result=0;
    while(i>0){
        result+=cnt[i];
        i -= (i & -i);  // Key code
    }
    return result;
}

2. 更新BIT结构中的值(更新当前值及高位累加值 ) :每次加上二进制的最后一个1位,直到二进制位的值 《=N;

公式:i += (i & -1)

void update(int i,int val){
    while (i<=n){
        cnt[i]+=val;
        i+= (i & -i); // Key code
    } 

}

3. 完整实现:

#include <cstdio>
#include <iostream>

using namespace std;

const int MAXI = 100002;
int cnt[MAXI];
int num[MAXI];
int n,val;
long long answer;
void update(int i,int val){
	while (i<=n){
		cnt[i]+=val;
		i+= (i & -i);
	} 

}
int get(int i){
	int result=0;
	while(i>0){
		result+=cnt[i];
		i -= (i & -i);
	}
	return result;
}
 
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&num[i]);
		cnt[i]=0;
	}
	answer = 0;
	for(int i=1;i<=n;i++){
		val = num[i];
		answer +=1ll*(get(n)-get(val));
	
		update(val,1); 

	}
	
	printf("%lld\n",answer);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值