• NYOJ 士兵杀敌(一) 树状数组 和 非树状数组两种AC方式总结


      因为是临时准备参加省赛,队友都使用c++,所以我也必须使用c++,一直以来我都是java编码更顺手,c++大量的编码实践还是缺乏的,所以这正是一个磨练的机会,果断上去。

    该题目有两种做法,我看到题目的时候只想到一种,没有想到第二种,后来同队的人说有第二种,让我到网上看,后深入的看了网友们不同的做法和见解,才了解到还有第二种。

    做这个题目的时候发现两个问题,第一cin、cout还是没有scanf和printf来的快,这点在ac中得到验证,另外就是多余的语句会同样增加速度。我因为只想到一种方法,因此努力改进编码,最后过了,后来看到第二种解法,眼睛瞪得老大啦~~

    士兵杀敌(一)

    时间限制:1000 ms  |  内存限制:65535 KB
    难度:3
     
    描述

    南将军手下有N个士兵,分别编号1到N,这些士兵的杀敌数都是已知的。

    小工是南将军手下的军师,南将军现在想知道第m号到第n号士兵的总杀敌数,请你帮助小工来回答南将军吧。

    注意,南将军可能会问很多次问题。

     
    输入
    只有一组测试数据
    第一行是两个整数N,M,其中N表示士兵的个数(1<N<1000000),M表示南将军询问的次数(1<M<100000)
    随后的一行是N个整数,ai表示第i号士兵杀敌数目。(0<=ai<=100)
    随后的M行每行有两个整数m,n,表示南将军想知道第m号到第n号士兵的总杀敌数(1<=m,n<=N)。
    输出
    对于每一个询问,输出总杀敌数
    每个输出占一行
    样例输入
    5 2
    1 2 3 4 5
    1 3
    2 4
    样例输出
    6
    9

    我的代码:

     1 //#include<iostream>
     2 #include<stdio.h>
     3 using namespace std;
     4 
     5 int main()
     6 {
     7     int n,m,tmp;
     8   scanf("%d%d",&n,&m);
     9 
    10     int a[n+1];
    11       a[0]=0;
    12 
    13 for(int i=1;i<n+1;i++){
    14     scanf("%d",&tmp);
    15 
    16  a[i]=a[i-1]+tmp;
    17 }
    18   for(int i=0;i<m;i++)
    19   { int b,d;
    20  scanf("%d%d",&b,&d);
    21 
    22   if(b==1) printf("%d\n",a[d]);
    23   else
    24   {
    25       printf("%d\n",a[d]-a[b-1]);
    26   }
    27 
    28 
    29   }
    30 
    31     return 0;
    32 }

    最优程序:

     1  
     2 #include<cstdio>
     3 const int MAX=1000010;
     4 int sum[MAX];
     5 int main()
     6 {
     7     int N,q,m,n;
     8     scanf("%d%d",&N,&q);
     9     for(int i=1;i<=N;++i)
    10     {
    11         scanf("%d",&sum[i]);
    12         sum[i]+=sum[i-1];
    13     }
    14     for(int i=0;i!=q;++i)
    15     {
    16         scanf("%d%d",&m,&n);
    17         printf("%d\n",sum[n]-sum[m-1]);
    18     }
    19 }        

    第二种解法,用到树状数组

     树状数组是一个查询和修改复杂度都为log(n)的数据结构,假设数组a[1..n],

    用lowbit函数维护了一个树的结构

    详情请看:http://baike.baidu.com/view/1420784.htm

    那么查询a[1]+...+a[n]的时间是log级别的,而且是一个在线的数据结构,

     支持随时修改某个元素的值,复杂度也为log级别。

     

      来观察这个图:

     

      令这棵树的结点编号为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的过程。

     

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

    第二种做法:

     1 #include"stdio.h"
     2 #include<string.h>
     3 int a[1000000];
     4 int main()
     5 {
     6         int n,sum;
     7         scanf("%d%d",&n,&sum);
     8         int i,j,k;
     9         memset(a,0,sizeof(a));
    10         for(i=1;i<=n;i++)
    11         {
    12              int num;
    13              scanf("%d",&num);
    14              j=i;
    15              while(j<=n)
    16              {
    17                     a[j]=a[j]+num;
    18                     j+=j&(-j);
    19              }
    20         }
    21         for(i=0;i<sum;i++)
    22         {
    23               scanf("%d%d",&k,&j);
    24               int s1=0,s2=0;
    25               k=k-1;
    26               while(k>=1)
    27               {          
    28                    s1=s1+a[k];
    29                    k-=k&(-k);
    30                }
    31                while(j>=1)
    32                {
    33                      s2=s2+a[j];
    34                      j-=j&(-j);
    35                }
    36                printf("%d",s2-s1);
    37                putchar('\n');
    38          }
    39          return 0;
    40 }       

     

     

     

  • 相关阅读:
    2.1求二进制数中1的个数
    SQL学习笔记八:SQL Server陷阱
    SQL学习笔记之三:系统表syscommnets
    SQL学习笔记之二:QUOTENAME函数
    SQL学习笔记五:脚本创建角色、用户以及相关操作
    输入表名生成插入、更新存储过程和调用该存储过程的字符串
    没有注意过的count(0),count(1),count(*),count(列名)
    清除开始文档批处理
    SQL学习笔记七:利用脚本附加数据库
    SQL学习笔记六:关于全备/差异/日志备份的恢复
  • 原文地址:https://www.cnblogs.com/zdcaolei/p/2484782.html
Copyright © 2020-2023  润新知