树状数组学习笔记

一.理论准备

image

 

        从图上看出:C[1] = A[1],C[2] = A[1] + A[2],C[3] = A[3],C[4] = A[1] + A[2] + A[3] + A[4],C[5] = A[5],C[6] = A[5] + A[6],C[7] = A[7],C[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8]。

二.发现规律

        1(0001)、3(0011)、5(0101)、7(0111):单项之和

        2(0010)、6(0110):连续两项之和

        4(0100):连续四项之和

        8(1000):连续八项之和

        假设二进制化后右端结尾0的个数为x,则改C数组项为A数组项的 2^x 项之和,因此C[i] 定义为由A数组某个数据开始累加到A[i]的和,累加的项数目为2^x。因此可以表示为C[i] = A[i - 2^x + 1]  + …… + A[i - 1] + A[i]。

        考虑解决前k项和S[k]的问题:以k=7为例,7的二进制为0111。1出现在第一位上,因此会有一个单项之和a[7],即C[7];去掉第一位的1,数据变成0110,因此会有一个连续二项之和,即C[6];去掉第二位的1,数据变成0100,因此还会有一个连续四项之和,即C[4]。表达出来为:

S[i] = C[i] + C[i-2^x] + ……直到i - 2^x <= 0。2^x的算法:i & (-i);假设i可以表示为A1B,其中B都为0,A' B'分别为A B的补码表示。(-i)二进制补码表示为取反加一:A'0B' + 1 = A'1B。因此i & (-i)就是A1B & A'1B = 1B = 2^x,得证。

        参考资料:http://blog.csdn.net/wyzxk888/article/details/7362286

三.二维树状数组

        C[x][y]=sum(A[i][j])。其中,x-lowBit(x)+1<=i<=x,y-lowBit(y)+1<=j<=y,二维树状数组一般就是对矩阵的操作,更新、求值。。。

四.Java实现

import java.util.Scanner;
public class POJ1195 {
  static int instruction;
  static int n;//阶数
  static int map[][];
  
  public static void main(String[] args) {
    
    Scanner sc = new Scanner(System.in);
    
    while(true) {
      instruction = sc.nextInt();
      if(0==instruction) {
        n = sc.nextInt();
        map = new int[n+2][n+2];
      }else if(3==instruction) {
        break;
      }else if(1==instruction) {
        int i = sc.nextInt()+1;
        int j = sc.nextInt()+1;
        int delta = sc.nextInt();
        update(i,j,delta);
      }else {
        int i = sc.nextInt()+1;
        int j = sc.nextInt()+1;
        int k = sc.nextInt()+1;
        int t = sc.nextInt()+1;
        /*
         * 注意下标,包括k行,不包括i行;包括t列,不包括j列
         * 俩wa
         */
        int ans = getSum(k,t) - getSum(i-1,t) 
            - getSum(k,j-1) + getSum(i-1,j-1);
        System.out.println(ans);
      }
    }
    
  }
  private static int getSum(int x, int y) {
    int sum = 0;
    /*
     * 下标从1开始,防止死循环
     * 因为若i或j是0,那么i-=lowbit(i)仍是0
     */
    for(int i=x; i>=1; i-=lowbit(i)) {
      for(int j=y; j>=1; j-=lowbit(j)) {
        sum += map[i][j];
      }
    }
    return sum;
  }
  private static void update(int x, int y, int delta) {
    
    for(int i=x; i<n+2; i+=lowbit(i)) {
      for(int j=y; j<n+2; j+=lowbit(j)) {
        map[i][j] += delta;
      }
    }
  }
  private static int lowbit(int x) {
    /*
     * x&(x^(x-1))
     * 个人感觉上面的好理解
     */
    return  x&(-x); 
  }
}

-------------------------------------------------------------------------------------------

import java.util.Arrays;
import java.util.Scanner;
/*
题目描述:给定n个数字保证唯一的序列,如果两个数a[i],a[j]要交换,那么代价为a[i]+a[j]
求让他们变成升序的最小代价
分析:其实这个结果和逆序数有关,对某个位置i,如果前面比他大的有x个,那么a[i]至少要加x次
如果后面有y个比a[i]小,那么a[i]至少要加y次,也就是说用两个树状数组来分别维护当前位置时前面有多少比他大后面有多少个比他小 
*/
/*
 * 原因很简单,大家都知道最少的交换次数是求逆序数。但是总花费最小是不是也是这样求?答案是:是。
 * 
 * 
 * 每次只能交换相邻两个数,否则1 5 3 4 2答案明显是交换5和2,就是7,实际是35
 */
public class HDU2838 {
  /*
   * 这道题和第二届省赛的一题类似
   * 当时用的是置换群
   */
  //这个是暴躁度
  static final int MAXN = 100010;
  static long[] sum = new long[MAXN];
  static int[] cnt = new int[MAXN];
  static int[] a;
  
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    while(sc.hasNext())  {
      Arrays.fill(cnt,0);
      Arrays.fill(sum,0);
      
      int n = sc.nextInt();
      a = new int[n+1];
      for(int i=1; i<=n; i++) {
        a[i] = sc.nextInt();
      }
      
      long ans = 0;
      //前面比他大的
      for(int i=n; i>=1; i--) {
        /*
         * modify函数在后面,防止多加本身
         */
        long num = getCnt(a[i]);
        ans += a[i]*num;
        ans += getSum(a[i]);
        modify(a[i]);
      }
      System.out.println(ans);
    }
  }
  private static void modify(int x) {
    long delta = x;
    while(x<MAXN) {
      cnt[x] += 1;
      //不是加x,否则测试都不过
      sum[x] += delta;
      x += lowbit(x);
    }
  }
  private static long getSum(int x) {
    
    long ans = 0;
    while(x>=1) {
      ans += sum[x];
      x -= lowbit(x);
    }
    return ans;
  }
  private static int lowbit(int x) {
    
    return x&(-x);
  }
  private static int getCnt(int x) {
    
    int ans = 0;
    while(x>=1) {
      ans += cnt[x];
      x -= lowbit(x);
    }
    return ans;
  }
}

---------------------------------------------------------------------------------------

import java.util.Scanner;
//点更新,求区间
public class HDU1166 {
  static int n;
  static int[] a;
  
  public static void main(String[] args) {
    int T;
    Scanner sc = new Scanner(System.in);
    T = sc.nextInt();
    int flag = 1;
    for(int j=1; j<=T; j++) {
      n = sc.nextInt();
      a = new int[n+1];
      for(int i=1; i<=n; i++) {
        modify(i,sc.nextInt());
      }
      System.out.println("Case "+flag+":");
      String str;
      while(true) {
        str = sc.next();
        if(str.equals("End")) {
          break;
        }else if(str.equals("Query")) {
          int from = sc.nextInt();
          int to = sc.nextInt();
          int ans = getSum(to) - getSum(from-1);
          System.out.println(ans);
        }else if(str.equals("Add")) {
          int index = sc.nextInt();
          int delta = sc.nextInt();
          modify(index,delta);
        }else {
          int index = sc.nextInt();
          int delta = sc.nextInt();
          modify(index,-delta);
        }
      }
      flag++;
    }
  }
  private static int getSum(int to) {
    
    int sum = 0;
    while(to>=1) {
      sum += a[to];
      to -= lowbit(to);
    }
    return sum;
  }
  private static void modify(int index, int nextInt) {
    
    while(index<=n) {
      a[index] += nextInt;
      index += lowbit(index);
    }
  }
  private static int lowbit(int index) {
    return  index&(-index); 
  }
}

----------------------------------------------------------------------------------------

//区间更新,求点
import java.util.Scanner;
/*
 *第一次涂色是对区间[A,B]涂色一次,可以让nNum[nA]++,nNum[nB+1]--即可。因为这样对于区间[0,nA-1]的任意值i有
都要nNum[1]+nNum[2]+...+nNum[i] = 0。而对于区间[nA,nB]的任意值i有nNum[1]+nNum[2]+...+nNum[i] = 0。
对于区间[nB+1, nN]的任意值i有nNum[1]+nNum[2]+...+nNum[i] = 0。
   那么重复多次了。如果上述求和nNum[1]+nNum[2]+...+nNum[i] 刚好代表每个结点i的涂色次数,那么这个题就可解了。
 * 
 * #include <stdio.h>
int a[100005];
int main()
{
    int i,n,p,q,s;
    while(scanf("%d",&n)==1 && n)
    {
        for(i = 1;i <= n;i ++)
            a[i] = 0;
        for(i = 1;i <= n;i ++)
        {
            scanf("%d %d",&p,&q);
            a[p] ++;
            a[q+1] --;
        }
        printf("%d",a[1]);
        s = a[1];
        for(i = 2;i <= n;i ++)
        {
            s += a[i];
            printf(" %d",s);
        }
        puts("");
    }
    return 0;
}
 */
public class HDU1556 {
  static int array[];
  static int n;
  
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int a,b;
    while(true) {
      n = sc.nextInt();
      array = new int[n+1];
      if(0==n) {
        break;
      }
      for(int i=0; i<n; i++) {
        a = sc.nextInt();
        b = sc.nextInt();
        modify(a, 1);
        modify(b+1,-1);
      }
      for(int i=1; i<=n; i++) {
        if(i<n) {
          System.out.print(getSum(i)+" ");
        }else {
          System.out.println(getSum(i));
        }
        
      }
    }
  }
  
  private static int getSum(int index) {
    
    int sum = 0;
    for(int i=index; i>=1; i -= lowbit(i)) {
      sum += array[i];
    }
    return sum;
  }
  
  private static void modify(int index, int delta) {
    while(index<=n) {
      array[index] += delta;
      index += lowbit(index);
    }
  }
  private static int lowbit(int x) {
    //下标是0的话死循环
    return x&(-x);
  }
}

 

原文地址:https://www.cnblogs.com/hxsyl/p/3264695.html