zoukankan      html  css  js  c++  java
  • HDU1166敌兵布阵树状数组入门

    树状数组,才刚开始做不久,这是第一道入门题目,可是发觉还是挺难,太抽象了,现在只能先记住模板函数(听好记得,优化成这种模式了)。待如后研究出来了,再补上吧哈。。其实理解不了,却不妨碍做题。如果有大牛看到这篇文章,能够顺便指导下,那就再好不过啦!

    下面这是从别人那里粘贴过来的(帮助理解):

    树状数组简介:
         树状数组是一种区间求和查询和元素修改的时间复杂度都在logn的线性的数据结构。它支持sigma(a[1], a[2], ... a[i]) 时间的复杂度为logn的查询,和对a[i]时间复杂度为logn的修改。
    来观察这个图:



      令这棵树的结点编号为c1,c2...cn。令每个结点的值为这棵树的值的总和,那么容易发现:
      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 lowbit(int x){

      return x&(x^(x–1));

      }

      当想要查询一个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的过程。
      对于数组求和来说树状数组简直太快了!

    数据范围很大。

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

    #include<iostream>
    #define lowbit(x) x&(-x)//这一步是树状数组的经典特点,位运算优化结果
    using namespace std;

    int m;
    int tree[50001];

    void add(int n,int num)//经典的树状数组更新数组函数
    {
     while(n<=m)
     {
      tree[n]+=num;
      n+=lowbit(n);
     }

    int subsum(int n)//讲点的树状数组求从1~n这一区间的累和,其实这里还可以变换成另一种标志模式,下一道会说到
    {
     int sum=0;
     while(n>=1)
     {
      sum+=tree[n];//每更新一个,那么其后面的每一个都要更新
      n-=lowbit(n);
     }
     return sum;
    }

    int main(void)
    {
     int n,count,s,e,l,num;
     char ch[100];

     cin>>n;
     count=0;
     while(n--)
     {
      memset(tree,0,sizeof(tree));
      cout<<"Case "<<++count<<":"<<endl;
       cin>>m;
      for(int i=1;i<=m;i++)
      {
       cin>>num;
       add(i,num);
      }
      for(i=0;i<=m;i++)
       cout<<tree[i]<<endl;
      while(cin>>ch,ch[0]!='E')
      {
       switch(ch[0])
       {
        case 'Q':
           cin>>s>>e;cout<<subsum(e)-subsum(s-1)<<endl;break;//求两区间的累和,用第二个区间减去第一个区间,但是第一个区间是少一的
        case 'A':
           cin>>l>>num;add(l,num);break;
        case 'S':
           cin>>l>>num;add(l,-num);break;    
       }
      }
     }
     return 0;
    }

  • 相关阅读:
    【Qt开发】01-第一个Qt程序Hello World!
    Git使用总结(三):协同开发常见冲突
    公钥、私钥、数字签名、数字证书、对称与非对称算法、HTTPS
    通俗理解TCP的三次握手
    Flink安装极简教程-单机版
    程序员工资那些事!
    vim实战:插件安装(Vundle,NerdTree)
    Git使用总结(二):分支管理
    Git使用总结(一):简介与基本操作
    C++11并发编程:多线程std::thread
  • 原文地址:https://www.cnblogs.com/cchun/p/2520070.html
Copyright © 2011-2022 走看看