理解树状数组与POJ 2352

学习自:链接以及百度百科

以及:https://www.bilibili.com/video/av18735440?from=search&seid=363548948825132979

理解树状数组

概念

假设数组a[1..n],那么查询a[1]+...+a[n]的时间是log级别的,而且是一个在线的数据结构,支持随时修改某个元素的值,复杂度也为log级别。

观察这棵树,容易发现:

  C1 = A1

  C2 = A1 + A2

  C3 = A3

  C4 = A1 + A2 + A3 + A4

  C5 = A5

  C6 = A5 + A6

  C7 = A7

  C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8

  ......

  C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16

  这里有一个有趣的性质:

  设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ax,

  所以很明显:Cn = A(n – 2^k + 1) + ... + An

  算这个2^k有一个快捷的办法,定义一个函数如下即可:

  int lowerbit(int x){
    return x&(x^(x–1));
  }

  利用机器补码特性,也可以写成:

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

  当想要查询一个SUM(n)(求a[n]的和),可以依据如下算法即可:

  step1: 令sum = 0,转第二步;

  step2: 假如n <= 0,算法结束,返回sum值,否则sum = sum + Cn,转第三步;

  step3: 令n = n – lowbit(n),转第二步。

  可以看出,这个算法就是将这一个个区间的和全部加起来,为什么是效率是log(n)的呢?以下给出证明:

  n = n – lowbit(n)这一步实际上等价于将n的二进制的最后一个1减去。而n的二进制里最多有log(n)个1,所以查询效率是log(n)的。

  那么修改呢,修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。

  所以修改算法如下(给某个结点i加上x):

  step1: 当i > n时,算法结束,否则转第二步;

  step2: Ci = Ci + x, i = i + lowbit(i)转第一步。

  i = i +lowbit(i)这个过程实际上也只是一个把末尾1补为0的过程。

  对于数组求和来说树状数组简直太快了!

  注:

  求lowbit(x)的建议公式:

  lowbit(x):=x and -x;

  或lowbit(x):=x and (x xor (x - 1));

  lowbit(x)即为2^k的值。

以上对树状数组的解释来自百度百科,比较难以理解。

  由图我们可以知道C8 是 A1+.....+A8,但是C6是 A5+A6,为什么要这么做?因为这样做会使操作更加简单,这样会使复杂度被log化。

C8可以看作A1......A8的左半边和+右半边和,而其中左半边和是C4,右半边其实也是同样的规则把a5......a8一分为二……继续下去都是一分为二直到不能分。树状数组也就是很巧妙的运用这种二分法来构建。

  那么,怎么实现这种二分法?lowbit(k)就是把k的二进制的高位1全部清空,只留下最低位的1,比如10的二进制是1010,则lowbit(k)=lowbit(1010)=0010。

比较普遍的方法lowbit(k)=k&-k,这是位运算。我们知道一个数加一个负号是把这个数的二进制取反然后+1,如k=10时,-10的二进制就是-1010=0101+1=0110,然后k&-k就是1010&0110,答案就是0010了!这样就可以把A数组和C数组联系在一起,设节点编号为x,C(x)是A(x)往左连续求lowbit(k)个数的和,比如lowbit(0110)=0110&0010=0010=2。C[0110]=A[0110]+A[0101]。可以看到其实只有低位的1起作用,因为很显然可以写出c[0010]=a[0010]+a[0001],这就为什么我们任何数都只关心它的lowbit,因为高位不起作用(基于我们的二分规则它必须如此!),除非除了高位其余位都是0,这时本身就是lowbit。

  

void add(int k,int num) {
       while(k<=n) {
            tree[k]+=num;
            k+=k&-k;
        }
}

【题目链接】

Stars

Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 45080   Accepted: 19567
  • Description

Astronomers often examine star maps where stars are represented by points on a plane and each star has Cartesian coordinates. Let the level of a star be an amount of the stars that are not higher and not to the right of the given star. Astronomers want to know the distribution of the levels of the stars. 

For example, look at the map shown on the figure above. Level of the star number 5 is equal to 3 (it‘s formed by three stars with a numbers 1, 2 and 4). And the levels of the stars numbered by 2 and 4 are 1. At this map there are only one star of the level 0, two stars of the level 1, one star of the level 2, and one star of the level 3.

You are to write a program that will count the amounts of the stars of each level on a given map.

  • Input

The first line of the input file contains a number of stars N (1<=N<=15000). The following N lines describe coordinates of stars (two integers X and Y per line separated by a space, 0<=X,Y<=32000). There can be only one star at one point of the plane. Stars are listed in ascending order of Y coordinate. Stars with equal Y coordinates are listed in ascending order of X coordinate.

  • Output

The output should contain N lines, one number per line. The first line contains amount of stars of the level 0, the second does amount of stars of the level 1 and so on, the last line contains amount of stars of the level N-1.

Sample Input

5
1 1
5 1
7 1
3 3
5 5

Sample Output

1
2
1
1
0
  • Hint

This problem has huge input data,use scanf() instead of cin to read data to avoid time limit exceed.

【题意】

  就是求每个小星星左小角的星星的个数。坐标按照Y升序,Y相同X升序的顺序给出 由于y轴已经排好序,可以按照x坐标建立一维树状数组。

#include <stdio.h>
#include <string.h>
const int MAXN=32005;
const int MINN=15005;
int tree[MAXN];//下标为横坐标
int level[MINN];//下标为等级数
/*int lowerbit(int x)
{
    return x&-x;
}*/
void add(int k,int num)
{
    while(k<=MAXN)
    {
        tree[k]+=num;
        k+=k&-k;
    }
}
int read(int k)//1~k的区间和
{
    int sum=0;
    while(k)
    {
        sum+=tree[k];
        k-=k&-k;
    }
    return sum;
}
int main()
{
    int n,x,y,i;
    memset(tree,0,sizeof(tree));
    memset(level,0,sizeof(level));
    while(scanf("%d",&n)!=EOF)
    {
        for(i=1;i<=n;i++)
        {
            scanf("%d%d",&x,&y);
            int temp=read(x+1);//加入x+1,是为了避免0,X是可能为0的
            level[temp]++;
            add(x+1,1);
        }
        for(i=0;i<n;i++)
            printf("%d\n",level[i]);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/fangxiaoqi/p/10202457.html

时间: 2024-11-03 05:27:29

理解树状数组与POJ 2352的相关文章

深入理解树状数组

树状数组(Binary Indexed Tree(BIT), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构.主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值:经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询). 百度上给出了令人难以理解的概念,其实这个东西我也是琢磨了一天,参考了大量博客的笔记才搞清楚了大致思路和原理,说说心得吧! 假设数组a[1..n],那么

【树状数组】POJ 2155 Matrix

附一篇经典翻译,学习 树状数组  http://www.hawstein.com/posts/binary-indexed-trees.html /** * @author johnsondu * @time 2015-8-22 * @type 2D Binary Index Tree * @strategy 如果翻转的是(x1,y1), (x2,y2)区域.则相当于 * 翻转(0, 0)~(x2, y2), 然后再翻转(0,0)~(x1-1, y2) * (0, 0)~(x2, y1-1),

【树状数组】POJ 3321 Apple Tree

/** * @author johnsondu * @time 2015.8.25 20:04 * @problem POJ 3321 Apple Tree * @type Binary Index Tree * @description 从根节点开始,dfs遍历树,先访问的节点 * 记为beg, 从当前结点遍历访问到的最后的 * 一个节点,记为end.然后按照树状数组的 * 方法进行求解. * @url http://poj.org/problem?id=3321 */ #include <i

小白初理解树状数组

ACM的在线测试里经常涉及到大量数据的的修改,求和等操作,这里介绍一种方法——树状数组. 树状数组,是一个查询和修改复杂度都为log(n)的数据结构.主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值:经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值.可以用一张图来弄懂什么是数组数组. 原数组A[n],树状数组C[n]; 如果n为奇数:Cn=An; 如果n为偶数:Cn = A(n – 2^k + 1) + ... + An,k为n的二进制数

理解树状数组

树状数组又名二分索引术,主要包含两种基本操作 1.Update(int i,int val)更新节点及其所有父节点及祖先节点的值,表示对第i点的值增加val.时间复杂度O(logn) 2.Sum(int i)表示对前i个点进行求和操作.时间复杂度O(logn),n表示节点总数,logn即log2n. 树状数组是通过数组来实现的一种轻量级的数据结构,性价比较高. 主要实现 定义数组C[i],A[i].C[i]=A[i-2^k+1]+A[i-2^k+2]+......+A[i],这里k表示i在二进制

(树状数组)POJ - 2299 Ultra-QuickSort

原题链接:http://poj.org/problem?id=2299 题意:求冒泡排序交换数字的次数 分析:起初听别人说挑战上的冒泡排序交换次数,一直是一头雾水.某天仔细想想,瞬间懂了. 其实就是把给数列按照给你的顺序插入到另一个数组中,插入的位置就是这个数在已经排好序的情况下所在的位置.所以前面的都是比它小,后面的都比它大. 通过很便捷的计算就能算出答案. for (int j = 0; j < n; j++)[ ans += j - sum(a[j]); add(a[j], 1); ] s

【树状数组】POJ 2309 BST

#include <iostream> #include <cstdio> #include <cmath> #include <cstring> #include <algorithm> #include <stack> #include <queue> #include <vector> using namespace std; /** * @author johnsondu * @time 2015-8-

POJ 2309 BST(树状数组Lowbit)

题意是给你一个满二叉树,给一个数字,求以这个数为根的树中最大值和最小值. 理解树状数组中的lowbit的用法. 说这个之前我先说个叫lowbit的东西,lowbit(k)就是把k的二进制的高位1全部清空,只留下最低位的1,比如10的二进制是1010,则lowbit(k)=lowbit(1010)=0010(2进制),介于这个lowbit在下面会经常用到,这里给一个非常方便的实现方式,比较普遍的方法lowbit(k)=k&-k,这是位运算,我们知道一个数加一个负号是把这个数的二进制取反+1,如-1

Mobile phones_二维树状数组

[题意]给你一个矩阵(初始化为0)和一些操作,1 x y a表示在arr[x][y]加上a,2 l b r t 表示求左上角为(l,b),右下角为(r,t)的矩阵的和. [思路]帮助更好理解树状数组. #include<iostream> #include<stdio.h> #include<string.h> using namespace std; const int N=1050; int c[N][N]; int s; int lowbit(int x) { r