代码改变世界

Enemy is weak 树状数组

2012-07-24 10:47  javaspring  阅读(477)  评论(0编辑  收藏  举报

来源:http://codeforces.com/problemset/problem/61/E

题意:给你一些数,让求满足i < j < k 且num[i] > num[j] > num[k]的序列的个数,而且已知每个数都是不同的。

思路:这是CF上的一道题,由题意很容易联想到逆序数。实际上就是这样,这道题就是和逆序数有关。假如所给的序列为10 8 3 1,那么我们求出每个数的后面比其小的有多少个,对于该序列来说的话为3,2,1,0。然后我们再求出每个数前面有几个比其大的,对于 该序列来说是0,1,2,3。现在我们把对应的比其小的数的个数和比其大的数的个数相乘再相加就是所求的答案。

          现在问题来了,用什么求逆序数。由于最多有100万个数,因此普通的方法求逆序数肯定超时。对于很多数求逆序数,一般有两种方法,归并排序或者是用树状数组求。我是用树状数组求得逆序数。先来说一下什么是树状数组。

        树状数组,顾名思义,就是一个数组,但是该数组和普通数组的区别在于,此数组是树状的。树状的含义就是含有节点,该结点所含有的信息和普通数组不同。我们先来考虑普通数组,num[1],num[2],num[3],,,普通数组里的每个元素所含的值就是其本身,而树状数组则不同。num1[1]所包含的元素是其自身,num1[2]所包含的元素是普通num[1]+num[2]的和,num1[3]所包含的元素是num[3],num1[4]所包含的元素是num[1] + num[2] + num[3] + num[4]的和,,,

       其实这里有个lowbit()函数,该函数就是求n到底包含普通数组里的哪些元素,其实就是n的二进制的最后一个1,详见http://blog.csdn.net/wmn_wmn/article/details/7754317里面有关于lowbit函数的介绍,其中涉及到了位运算。

      由于树状数组的独特结构,因此树状数组相较普通数组的某些操作来说就有了log(n)的复杂度,因此用途非常大。

       树状数组一般有两种操作,插点问线和插线问点。插点问线就是说数组中的某个元素增加多少或者减少多少,然后问某个区间的和。我们先来考虑此题用普通数组怎么求。倘若我们用普通数组的话,首先需要更新增加或者减少的元素,然后循环区间求和,时间复杂度主要在于循环求和的地方,为O(n)的复杂度。我们再来想树状数组,树状数组由于其独特的性质,因为节点所包含的信息不仅仅是其自身,而是某些元素的和,因此我们在求和的时候可以不用循环每个元素求,只需要按照lowbit()函数来求,实际上就是枚举二进制中的1,这样就大大提高了效率。然后我们想更新操作,由于树状数组中的元素不仅仅是其自身,因此我们在更新某个元素时,需要一直向后更新。举例来说,如果2结点增加,则我们在更新时不仅仅需要更新2,还需要更新4,8,,,因为这些节点包含了2节点,因此若2结点改变,则4结点也应该改变。求和时,假如区间为(a,b),我们只需求出前b个元素的和,然后再求出前a个元素的和,两者想减即可。

      树状数组的另一种操作时插线问点,即某个区间里的每个元素都更新,多个区间更新后,最后问某个节点的元素是多少。我们还用和插点问线同样的思路,先想若是普通数组进行这样的操作应该如何处理。易知,若是普通数组,每次区间更新都要循环区间里的每个元素进行更新,最后询问结点元素时直接输出即可。其实最后问的结点元素的值就是所有更新区间对其造成影响的和。其时间复杂度主要在更新操作上,为O(n)。我们再来考虑树状数组,若更新的区间为(a,b),则我们只需要让b以前(包含b)的每个元素都加上更新值value,让a以前的每个元素都加上-value即可,这其实就是插点问线中的求和操作。然后询问结点元素时,我们也是求对其造成影响的所有区间的和,考虑更新操作,发现每次更新都是向前更新,也就是说,若一个区间对某个节点造成了影响,则该区间一定有端点在该点的右面。因此我们查询时向后更新。也就是说,插线问点和插点问线是两种相反的操作。

     现在我们来想如何用树状数组求逆序数。假设有n个元素,我们每输入一个元素就插入该元素并更新。我们可以求出该元素num[i]的前面有几个元素比他小,然后用i - query(num[i]) 就可以得出其前面有几个元素比他大。因此我们每次都将num[i] 向后更新,值加1,然后查询时向前查询即可。这样就把逆序数求出来了。

      由于这道题数据特殊,最多有100万个数,数最多可以是10亿,因此需要离散化一下。。。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string.h>
using namespace std;

#define CLR(arr,val) memset(arr,val,sizeof(arr))
const int M = 1000010;
int num[M],num2[M],bignum2[M],lowdit[M];
__int64 bignum[M],smallnum[M];
struct dit{
	int id,num;
}dd[M];
struct newdit{
	int id,num;
}newdd[M];
void init(){
	CLR(num,0);
	CLR(bignum,0);
	CLR(bignum2,0);
	CLR(smallnum,0);
	CLR(num2,0);
}
bool cmp(dit a,dit b){
	return a.num < b.num;
}
bool cmp1(newdit a,newdit b){
	return a.id < b.id;
}
int lowbit(int x){
	return x&(-x);
}
void update(int x){
	while(x < M){
	  lowdit[x] += 1;
	  x += lowbit(x);
	}
}
int query(int x){
	int s = 0;
	while(x > 0){
	  s += lowdit[x];
	  x -= lowbit(x);
	}
	return s;
}
int main(){
	//freopen("1.txt","r",stdin);
	int n;
	while(scanf("%d",&n) != EOF){
	  init();
	  int cnt = n;
	  for(int i = 1; i <= n; ++i){
	    scanf("%d",&dd[i].num);
		dd[i].id = i;
	  }
	  sort(dd+1,dd+n+1,cmp);
	  int K = 1;
	  for(int i = 1;i <= n;++i){
	    newdd[i].id = dd[i].id;
		newdd[i].num = K++; 
	  }
	  sort(newdd+1,newdd+n+1,cmp1);
	  for(int i = 1;i <= n; ++i)
		  num[i] = newdd[i].num; 
	  for(int i = 1; i <= n; ++i){
		num2[cnt--] = num[i];
		update(num[i]);
		bignum[i] = i - query(num[i]);
	  }
	  CLR(lowdit,0);
	  for(int i = 1;i <= n;++i){
	    update(num2[i]);
		bignum2[i] = i - query(num2[i]);
		smallnum[i] = i-1 - bignum2[i];
	  }
	  __int64 sum = 0;
	  cnt = n;
	  for(int i = 1;i <= n;++i)
		  sum += (bignum[i] * smallnum[cnt--]);
	  printf("%I64d\n",sum);
	}
	return 0;
}