赞
踩
看到log n,很容易猜到树状数组利用了二分的思想。c数组的每一项,表示了在这一项的“管辖范围”(图中实线所连接的点)之内的元素之和;而当要查询比如区间[1, 7]时,可以将它拆分成c[4]+c[6]+c[7]。那么如何确定一个数的管辖范围,又如何拆分呢?不妨看一下对 i 的二进制而言,c[i]的管辖范围有什么特点。
许多其他博客都有对这个的讲解,这里就不再多说了。
- inline int lowbit(int i) {
- return i & (-i);
- }
至于为什么,暂时还没有找到一个清晰而且容易理解的证明……不过举几个例子很容易验证。
把这个公式一直往上代,直到超出n为止,这样得到的每一个点都是管辖着a[i]的,那么修改代码写出来如下:
- void modify(int x, int add) {
- do c[x] += add;
- while ((x += lowbit(x)) <= n);
- }
- int q(int r) {
- int sum = 0;
- do sum += c[r];
- while (r -= lowbit(r));
- }
- inline int q_range(int l, int r) {
- return q(r) - q(l - 1);
- }
自然,树状数组除了单纯作为一个常数比线段树小的单点修改、区间查询的数据结构以外,还有其他的应用,比如求逆序对一类的问题。逆序对除了双重循环O(n^2)的暴力以外,还有一种“另类”(O(nm))的暴力方法:
利用桶排的思想,每次放入一个数,统计比它大的数的个数;其中m是桶的大小。
写出这个方法的代码以后,发现需要不断地修改一个点与扫描区间。单点修改恰好是树状数组的特长,那么只需要试图把扫描区间变化成区间求和,就可以将时间复杂度减为O(nlog n)。可以注意到,每放入一个数,相当于后来来到的比它小的数的逆序对数都需要增加1;那么不妨设置一个数组a,每插入一个数就在对应的地方加上1,并将逆序对数加上从它(不包括)到末尾的区间和。
当数字跨越很大的时候,这是否又太占空间了呢?因此可以使用离散化思想:只需要记录一个元素在原来数组中是第几大就可以了,代码如下:(其中a[i]是a数组中第h[i]大的数)
- struct cmp {
- bool operator()(int x, int y) {
- return a[x] > a[y];
- }
- }
-
- int main() {
- //读入数据到a数组中
- for (int i = 0; i < n; ++i)
- h[i] = i;
- sort(h, h + n, cmp());
- //继续下面的操作
- }
- #include<iostream>
- #include<algorithm>
- #include<numeric>
- using namespace std;
-
- //l[i]是左侧小于a[i]的数的个数;r[i]是右侧大于a[i]的数的个数
- //c是l的树状数组,C是r的树状数组,n是数列长度,其余字母如上
- int c[30010], C[30010], a[30010], h[30010], n, l[30010], r[30010], k[30010];
- struct cmp { bool operator()(int x, int y) {return a[x] < a[y];}};
-
- void p(int* w, int x) {do ++w[x]; while ((x += (x & (-x))) <= n);}
- int q(int* w, int x) { int s = 0; do s += w[x]; while (x -= (x & (-x))); return s; } //树状数组基本操作;p:单点修改 q:区间查询
-
- void unique() { //给重复数字同一编号
- int d = k[h[1]] = 1;
- for (int i = 2; i <= n; ++i) {
- if (a[h[i]] != a[h[i - 1]]) ++d;
- //如果不同,编号自增;也就是说,如果相同就会有同一编号
- k[h[i]] = d;
- }
- }
-
- int main() {
- cin >> n;
- for (int i = 1; i <= n; ++i) cin >> a[i], h[i] = i;
- sort(h + 1, h + n + 1, cmp()); unique() //离散化
- for (int i = 1; i <= n; ++i)
- p(c, k[i]), l[i] = q(c, k[i] - 1); //注意查询左边区间和的时候并不包括自己,因此要用k[i] - 1
- for (int i = n; i >= 1; --i)
- p(C, k[i]), r[i] = n - i - q(C, k[i]) + 1; //这里要查询的是右边的区间和,可以用总和(也就是n-i+1)减去左边的区间和q(C, k[i])
- cout << inner_product(l + 2, l + n, r + 2, 0) << endl;
- return 0;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。