首页 > 代码库 > 树状数组求逆序数 。,。 蓝桥杯 小朋友排队

树状数组求逆序数 。,。 蓝桥杯 小朋友排队

先介绍一下树状数组。什么是树状数组呢?树状数组(Binary Indexed Tree(BIT), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。(度娘万岁)

假设A[]数组为存储原来的值得数组,C[]为树状数组。

我们定义:C[i] = A[i - 2^k + 1] + ..... + A[i]  其中k为i用二进制表示时的末尾0的个数。例如:i= 10100,则k = 2,i = 11000,则k = 3;为了方便我直接写的是二进制的i,请读者注意。

先介绍一下lowbit 吧  lowbit(x)=2^k k表示x在二进制形式表示下末尾0的个数比如 11000

lowbit(11000)=8 这里的lowbit是树状数组的精华 许多操作都和这个有关

lowbit(int x)//利用机器码补码的原理来求lowbit

{

    return x&(-x);

}

先说说树状数组更新的过程

我们举个例子(为了方便i的值直接写成二进制了):i = 11000,此时k = 3;

这2^k = 3 个数即为:A[11000],A[10111],A[10110],A[10101],A[10100],A[10011],A[10010],A[10001]

C[11000] = A[11000]+A[10111]+A[10110]+A[10101]+A[10100]+A[10011]+A[10010]+A[10001];

这里我们会发现:

A[10100]  + A[10011] +  A[10010] + A[10001] = C[10100]

A[10010] + A[10001] = C[10010]

A[10011] = C[10011]

所以

C[10100] = C[10010] + C[10011] + A[10100]

 

A[10110] + A[10101] = C[10110]

A[10101] = C[10101]

所以

C[10110] = C[10101] + A[10110]

 

A[10111] = C[10111]

 

至此我们可以得出:

C[11000] = C[10100] + C[10110] + C[10111] + A[11000]

 

到这里我们可以得出:

k的值就表示子树的个数,子树即为树状数组的元素。

那么更新的时候怎么更新父节点呢? 这里就要用上lowbit了 

由于子树都是由父节点一步一步拆分而来 所以可以依葫芦画瓢 还原回去

由上可以观察出 c[]中 子节点是父节点最高位1不断右移动的过程 还原的话 p=i+lowbit(i) p为父节点 i为子节点 有了这个 updata就很容易实现了


updata(int i,int x,int n)//i表示节点, x表示值,n表示范围
{
while(i<n)
{
c[i]+=x;
i=i+lowbit(i);
}
}

最后说说求和的问题

我们既然定义了c[]数组 那么getsum(x)的时候 当然是要用上 c[]的 那么怎么用呢?

我们知道c[i]存储了i到i-2^k+1数之和 那我们要求1~i和的时候 c[i]数去了2^k个数

我们让i-2^k 这个时候 最低位的1消去了 然后我们重复上述过程 不断的消去最低位1  就可以求出1~i的和了

int getsum(int i)
{
int sum=0;
while(i>0)
{
sum+=c[i];
i=i-lowbit(i);
}
return sum;
}

 

好了这里介绍完了树状数组 现在来说下怎么求逆序对

上篇介绍了用归并思想求逆序数 这里还可以用权值线段树的思想来求

给定一个数列 比如 1 8 9 2 7 3  我们定义一个区间(如果区间太大可以用离散话对应)

然后将这些数据依次插入 然后看在插入的时候 有多少比他大的数在区间里面 这就是这个数造成的逆序数 统计起来就是这个序列的逆序数了

 

对于这道题目而言关键是要把这道题目巧妙的转化为求逆序对的问题

是最简单的关于逆序对的题目,题目大意是给出一个序列,求最少移动多少步可能使它顺序,规定只能相邻移动。
相邻移动的话,假设a b 相邻,若a<b 交换会增加逆序数,所以最好不要做此交换;若a==b 交换无意思,也不要进行此交换;a>b时,交换会减少逆序,使序列更顺序,所以做交换。
由上可知,所谓的移动只有一种情况,即a>b,且一次移动的结果是逆序减1。假设初始逆序是n,每次移动减1,那么就需要n次移动时序列变为顺序。所以题目转化为直接求序列的逆序便可以了

 引用自http://blog.csdn.net/suwei19870312/article/details/5295135

#include <stdio.h>
#include <string.h>

#define maxn 1000010
typedef long long LL;

LL T1[maxn], T2[maxn], unhappy[maxn];
LL A[maxn], B[maxn], N; // B[]存储交换次数

LL lowBit(LL x) { return x & -x; }

void update(LL x, LL *arr) {
	for ( ; x < maxn; x += lowBit(x))
		++arr[x];
}

LL getSum(LL x, LL *arr) {
	LL sum = 0;
	for ( ; x; x -= lowBit(x))
		sum += arr[x];
	return sum;
}

int main() {
	
	LL i, j;
	LL ret = 0;

	for (i = 1; i < maxn; ++i)
		unhappy[i] = unhappy[i-1] + i;

	scanf("%lld%lld", &N, &A[0]);
	update(A[0] + 1, T1);
	for (i = 1; i < N; ++i) { // 当前点跟左边的点产生的逆序对数
		scanf("%lld", &A[i]);
		update(A[i] + 1, T1);
		B[i] = i+1 - getSum(A[i] + 1, T1);
	}
	for (i = N - 1; i >= 0; --i) { // 当前点跟右边的点产生的逆序对数
		update(A[i] + 1, T2);
		ret += unhappy[B[i] + getSum(A[i], T2)];
	}

	printf("%lld\n", ret);

	return 0;
}

前面的一些整理引用了大佬http://blog.csdn.net/ljd4305/article/details/10101535一些引例

 

树状数组求逆序数 。,。 蓝桥杯 小朋友排队