逆序对---树状数组

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a_i>a_jai​>aj​ 且 i<ji<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

解答

        逆序对在一开始学习归并排序的时候就有了一个O(nlogn)级别的解法,已经是比较快了,然后今天学习了一下树状数组,找到了另外一个O(nlogn)的解法,但在这一道题上面可能还会慢一点,借鉴了一些别人的题解。

        统计一个序列中有多少个逆序对,也就是找每个数字前面有多少个比这个数字大的。只要每进来一个数字,我就能直接查询到多少个数字比这个数字要大,那么任务就可以完成,因此可以用一个后缀和数组来维护每次加入这个数字后,比这个数字大的数字的数目,从而实现快速查询,就比如加入了3,那么1,2都要加1,因为1,2,都多了一个比这个数字大的数目,然后再进来2的话,这时候2已经是1了,直接加1到答案中,不过每次给区间都增加是耗时的,这时候引入差分数组,对点进行更新,也就是树状数组添加操作;同时对后缀和求和就可以得到每个点的值,这其实就是树状数组的查询操作。

两个问题

        1. 这里的数据范围是1e9,所以要维护这么大的范围的数据其实是不现实的,无论是空间还是时间都是不可以的,但是这里比较特殊的是数字之间只有大小的关系,1 2 3和1 2 10本质是一样的,因此只要将数组排序之后用编号给各个数字重新赋值,利用形式大小来进行统计就可以,比如1 10 100直接变换为1 2 3,这样1e5的维护长度是可以接受的。

        2. 关于后缀和的我还是不太清楚,因此我将其转化为了求前缀和,这样差分求和也比较熟悉直观,区别就是要反过来,本来应该大的变为小的,也就是把1 10 100转为了 3 2 1,然后统计每个数加入时有多少个比它小的,道理还是一样的,为什么转和怎么转如下。

           下面详细说一下排序这里。每个数字可以读为一个结构体,里面存放了每个数字的号次和数字大小,然后对结构体数组排序,先按照大小排序,大小一样的话,就把先放进来的放在前面。

        具体过程是这样的,需要统计每次进入一个值后有多少个比这个小的值。比如1 3 3 2,首先排序为3(2) 3(3) 2(4) 1(1),括号里面是出现的号次,观察第一个是在第二个出现,我们现在希望把第二个出现的数字等效替换为一个数字,这个数字就是编号,所以f[2]=1,重复操作,重新编号为4 1 2 3。设定初值ans=0,差分数组b,第一个拿到4,发现b(4)为0,也就是比4小的没有,ans还是0,更新b[5]+1,代表的是所有比4大的+1,这样假设来了一个5,那么差分求和,sum(5)=b(1)+...b(5)=1,就可以加到ans里面去。

        这个解法因为涉及到排序,也是O(nlogn)所以可能比归并要慢一点,但是如果不用排序,也就是数据范围小,那就不一定了。

下面是代码: 

#include<stdio.h>
#include<algorithm>
#define ll long long
using namespace std;
int n;
ll ans = 0;
int f[600000];
int t[600000];
struct node
{
	int num;//实际数据
	int bh;//编号
}a[600000];

int lowbit(int x)
{
	return x & (-x);
}

void add(int x, int k)
{
	while (x <= n)
	{
		t[x] += k;
		x = x + lowbit(x);
	}
}

int ask(int x)
{
	int ans = 0;
	while (x)
	{
		ans += t[x];
		x -= lowbit(x);
	}
	return ans;
}

int cmp(node x, node y)
{
	if (x.num != y.num)
	{
		return x.num > y.num;
	}
	else
	{
		return x.bh > y.bh;
	}
}
int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &a[i].num);
		a[i].bh = i;
	}
	sort(a + 1, a + n + 1, cmp);//排序,大小相同,次序优先,保证相同的值不能算逆序对
	for (int i = 1; i <= n; i++)//重新编号操作
		f[a[i].bh] = i;
	for (int i = 1; i <= n; i++)//树状数组
	{
        //利用差分计算
		ans += ask(f[i]);
		add(f[i] + 1, 1);
	}
	printf("%lld", ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_60360239/article/details/128425011