树状数组首先是用来求和的, 但是这个和的具体意义不同,树状数组解决的问题就不同。
我做的第一道树状数组的题是POJ2299 Ultra--QuickSert ,这道题是求,要将一个序列非降序排列,需要做多少次交换。而实际上就是求逆序数。
求逆序数是一维树状数组的一个重要应用,POJ3067 Japan也是一个求逆序数的题,还有POJ2481Cow 也是这个类型的,那我们就来总结一下,用树状数组求逆序数的方法。
树状数组求逆序数:
首先,我们按照给定的数组的顺序构造树状数组,在构造的过程中,来求没每个数的逆序数。
例如, 1 4 2 3 5 3 8 3
对于这个序列, 此时我们将树状数组初始化为memset(bit, 0, sizeof(bit)), 那么我们做另外一件事,就是记录构造树的经过,我们用数组a[ ]来表示, 那么初始化的时候就是 0 0 0 0 0 0 0 0, 也就是说数组a是一个为表示, 他表示相应位置的数字的个数, 那么第一个操作过后, a成为了1 0 0 0 0 0 0 0, 但是此时纵观数组a,比1大的位置没有其他的数, 所以你序数为0;第二个操作过后, a:1 0 0 1 0 0 0 0, 此时还是没有逆序数, 但是第三个操作后, a:1 1 0 1 0 0 0 0 , 很显然, 比2的位置大的位置, 有一个4的位置是有数字的, 那么2的逆序数为1, 这个时候,一共有3个被操作了, 而小于等于2的有两个, 也就是Sum(2), 那么逆序数的个数就是3 - 2 = 1, 以此类推。
还有一点, 那就是数组是按照原序列的顺序来安置的, 所以求出来的就是这个数在原序列的位置之前比它大的数的个数。和后面的无关。
那么POJ2299的代码, 如下:
#include <stdio.h> #include <algorithm> using namespace std; #define N 500010 int n, a[N], c[N]; struct node { int v, org; }p[N]; bool cmp ( node a, node b ) { return a.v < b.v; } int lowbit ( int x ) { return x & ( -x ); } void addpoint ( int x ) { for ( ; x <= n; x += lowbit(x) ) c[x]++; } int sum ( int x ) { int s = 0; for ( ; x > 0; x -= lowbit(x) ) s += c[x]; return s; } int main() { while ( scanf("%d", &n) != EOF && n ) { long long ans = 0; for ( int i = 1; i <= n; ++i ) { scanf( "%d", &p[i].v ); p[i].org = i; } sort ( p + 1, p + n + 1, cmp ); for ( int i = 1; i <= n; ++i ) a[p[i].org] = i, c[i] = 0; for ( int i = 1; i <= n; ++i ) { addpoint(a[i]); ans += i - sum ( a[i] ); } printf ( "%lld\n", ans ); } }