用树状数组解决逆序对问题【超详细】(以一本通1311题为例)

用树状数组解决逆序对问题(以一本通1311题为例)

一、关于树状数组
树状数组具体的原理在此处不提,如果有想要了解的可以点击以下视频链接(强烈推荐,讲的十分清楚)

〔manim | 算法 | 数据结构〕 完全理解并深入应用树状数组

1.int lowbit(int x):返回数x二进制从后向前第一个为1的数(例如7的二进制表达为0b0111,函数的返回2的0次方1;8的二进制表达为0b1000,返回2的3次方8)。

2.void (int index):将位置为index的数加一。

3.int ask(int index):返回该数组index以及之前的数之和。

下面是树状数组具体实现代码(至于为什么要将数组初始化为0涉及到之后的算法):

int lowbit(int x) {
	return x & (-1 * x);
}
class tree_list {
public:
	void add(int index) {
		for (; index <100005; index += lowbit(index)) {
			arry1[index]++;
		}
	}
	int ask(int index) {
		int ans = 0;
		for (; index ; index -= lowbit(index)) {
			ans += arry1[index];
		}
		return ans;
	}
private:
	long long int arry1[100015] = {0};
};

二、求逆序数
先列举一个数组 arry[5]={5,4,3,2,1}
用传统的方法向后枚举可以和每个数组成逆序对的数。这样一个长度为n的数组的第i个数就要枚举i*n-i次,而这样做的时间复杂度为o(n^2)。这样处理的每次数据无法对下一次处理的数据做出贡献,所以我们会浪费很多时间,用下面的方法可以将前一次处理的结果为下一次用,可以将时间复杂度缩减为o(nlog(n))

算法原理:
我们从后面向前扫描并且开一个数组用来arry[i]来储存此时i出现的次数,还是以arry[5]={5,4,3,2,1}为例:
初始时
a[6]={0,0,0,0,0,0}
注意,arry储存的为a[i]大小的数出现的次数,所以arry的大小应该为a中最大的数+1
第一次扫描:
arry[5]=1,我们在arry[a[5]]的位置加一,
此时
a=[0,1,0,0,0,0]

在a[arry[5]]之前的数之和为arry[5]=1的逆序数,为0
第二次扫描
arry[4]=2
此时
a=[0,1,1,0,0,0]

a[arry[4]]前数的和为arry[4]=2的逆序数,为1
也就是说在i时刻a[i]的逆序数为arry[a[i]]之前的所有数之和
根据树状数组线上求和的特点,便可以解决该问题。
代码如下(还是以该题为例):

int main() {
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> arry[i];
	}
	tree_list solution_list;//创建应该树状数组对象
	long long int ans = 0;
	for (int i = n - 1; i >= 0; i--) {
		solution_list.add(arry[i]);//记录i时arry【i】出现的次数
		ans+=solution_list.ask(arry[i]-1);//查看arry【i】前比他小的数的和并且加到最后的答案中去
	}
	cout << ans;
}	

AC代码:

#include<iostream>
#include<vector>
using namespace std;
int arry[100015];
int lowbit(int x) {
	return x & (-1 * x);
}
class tree_list {
public:
	void add(int index) {
		for (; index <100005; index += lowbit(index)) {
			arry1[index]++;
		}
	}
	int ask(int index) {
		int ans = 0;
		for (; index ; index -= lowbit(index)) {
			ans += arry1[index];
		}
		return ans;
	}
private:
	long long int arry1[100015] = {0};
};
int main() {
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> arry[i];
	}
	tree_list solution_list;
	long long int ans = 0;
	for (int i = n - 1; i >= 0; i--) {
		solution_list.add(arry[i]);
		ans+=solution_list.ask(arry[i]-1);
	}
	cout << ans;
}	

该题链接一本通[1311]
该题在力扣对应题目剑指offer.51
如有错误欢迎纠正

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值