编程学习笔记12--树状数组的运用

本文探讨了士兵杀敌问题的解决方案,并引入了树状数组这一数据结构,详细解释了其定义、基本操作及其在解决此类问题时的优势。通过实际案例,展示了如何利用树状数组高效地处理区间查询和更新操作。

实例问题

士兵杀敌(一)

时间限制:1000 ms  |  内存限制:65535KB

难度: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 34 5

1 3

2 4

样例输出

6

每次都累加,求范围的时候用末减去初就可以了。

#include<stdio.h>
#include<string.h>
#define M 1000000+10
int sum[M]={0};
int main()
{
    int n,m,i,t,s=0;
    int begin,end;
    scanf("%d %d",&n,&m);
    for(i=1;i<=n;i++)
    {
        scanf("%d",&t);
        s+=t;
        sum[i]=s;
    }
    while(m--)
    {
        scanf("%d %d",&begin,&end);
        printf("%d",sum[end]-sum[begin-1]);
    }
    return 0;
}

士兵杀敌(二)

时间限制:1000 ms  |  内存限制:65535KB

难度:5

描述

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

 

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

 

南将军的某次询问之后士兵i可能又杀敌q人,之后南将军再询问的时候,需要考虑到新增的杀敌数。

 

 

 

输入

只有一组测试数据

第一行是两个整数N,M,其中N表示士兵的个数(1<N<1000000),M表示指令的条数。(1<M<100000)

随后的一行是N个整数,ai表示第i号士兵杀敌数目。(0<=ai<=100)

随后的M行每行是一条指令,这条指令包含了一个字符串和两个整数,首先是一个字符串,如果是字符串QUERY则表示南将军进行了查询操作,后面的两个整数m,n,表示查询的起始与终止士兵编号;如果是字符串ADD则后面跟的两个整数I,A(1<=I<=N,1<=A<=100),表示第I个士兵新增杀敌数为A.

输出

对于每次查询,输出一个整数R表示第m号士兵到第n号士兵的总杀敌数,每组输出占一行

样例输入

5 6 1 2 3 4 5 QUERY 1 3 ADD 1 2 QUERY 1 3 ADD 23 QUERY 1 2 QUERY 1 5

样例输出

6 8 8 20

这段代码可以通过,但是不知道是不是会超时微笑,因为不是题目不是在OJ上看到的。但是八成会超时,不然还学什么树状数组呢大笑,那么我们就假设会超时吧闭嘴
#include<stdio.h>
#include<string.h>
#define MAX 1000000+10
int sum[MAX]={0};
int main()
{
    int M,N,i,t=0,s=0;
    int begin,end;
    char cmd[80];
    //freopen("input.txt","r",stdin);
    scanf("%d %d",&N,&M);
    sum[0]=0;
    for(i=1;i<=N;i++)
    {
        scanf("%d",&t);
        s+=t;
        sum[i]=s;

    }
    while(M--)
    {
        scanf("%s %d %d",cmd,&begin,&end);
        if(strcmp(cmd,"QUERY")==0)
        {
            printf("%d\n",sum[end]-sum[begin-1]);
        }
        else if(strcmp(cmd,"ADD")==0)
        {
            for(t=begin;t<=N;t++)
            {
                sum[t]+=end;
            }
        }
    }
    return 0;
}

联想:疑问第二个问题我想起了自己买火车票的过程,假设线路是哈尔滨-吉林-四平-唐山-衡水-清河城。我每次查询区间内的票的数量的过程就是题目里面的将军询问杀敌数的过程,士兵杀敌数的变化相当于人在买票的过程得意,比如一个人买了哈尔滨到清河城的票,那么区间内所有的票数目都要减去1.如果只是买到了吉林,那四平和后面的就不用变了。如果上述问题就是铁道部卖票的过程的一个抽象,我上述的代码,在那么多买票的人情况下,一定会让人感觉卡的要死尴尬,或者根本不能用,所以还是学树状数组吧。

树状数组

树状数组的作用

平常我们会遇到一些对数组进行维护查询的操作,比较常见的如,修改某点的值、 求某个区间的和,而这两种恰恰是树状数组的强项!当然,数据规模不大的时候,对于修改某点的值是非常容易的,复杂度是O(1),但是对于求一个区间的和就要扫一遍了,复杂度是O(N),如果实时的对数组进行M次修改或求和,最坏的情况下复杂度是O(M*N),当规模增大后这是划不来的!而树状数组干同样的事复杂度却是O(M*lgN),别小看这个lg,很大的数一lg就很小了。

树状数组的定义






如图所示,红色矩形表示的数组C[]就是树状数组。

给定序列(数列)A,我们设一个数组C满足
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
C9=A9,

C10=A9+A10,

C11=A11

C12=A9+A10+A11+A12

C13=A13

C14=A13+A14

C15=A15

C16=A1+A2+A3+A4+A5+.......+A16

定义:
   C[t] = A[t – 2^k + 1] + … + A[t],k为t在二进制下末尾0的个数。(t>=1)

分析上面的几组式子可知,当 t 为奇数时,Ct=Ai ;当 t为偶数时,就要看 t的因子中最多有二的多少次幂,例如,6 的因子中有 2 的一次幂,等于 2 ,所以 C6=A5+A6(由六向前数两个数的和),4 的因子中有 2 的两次幂,等于 4 ,所以 C4=A1+A2+A3+A4(由四向前数四个数的和)。


基本操作

C[t]展开以后有多少项:

int lowbit(int t)//展开的项数
{
     return t&(-t);
}

修改

 当我们修改A[i]的值时,可以从C[i]往根节点一路上溯,调整这条路上的所有C[]即可,对于节点i,父节点下标p=i+lowbit(i)  

update(int i,int x)//增加某个元素的大小
{   
	  while(i<=n)
          {   
	    c[i]=c[i]+x;   
	    i=i+lowbit(i);   
	  }   
}   

求数列的前n项和

只需找到n以前的所有最大子树,把其根节点的C加起来即可

int Sum(int n) //求前n项的和.  
	{   
	    int sum=0;   
	    while(n>0)   
	    {   
	         sum+=c[n];   
	         n=n-lowbit(n);   
	    }       
	    return sum;   
	}   
 Sum(1)=C[1]=A[1];  
 Sum(2)=C[2]=A[1]+A[2];  
 Sum(3)=C[3]+C[2]=A[1]+A[2]+A[3];  
 Sum(4)=C[4]=A[1]+A[2]+A[3]+A[4];  
 Sum(5)=C[5]+C[4];  
 Sum(6)=C[6]+C[4];  
 Sum(7)=C[7]+C[6]+C[4];  
 Sum(8)=C[8];  



不过需要注意的是如果数组从0开始计数或者是数据中包含0的时候,就要处理一下,因为 0 的lowbit(0) 返回的值 就是 0 ,这样就会导致死循环。

//模板:
#include<stdio.h>
#define MAXN 10
int n;//数组元素个数

int c[MAXN]; //对应的树状数组
int lowbit(int x)
{
    return x & (-x);
}
void modify(int x,int add)//一维
{
    while(x<=MAXN)
    {
        c[x]+=add;
        x+=lowbit(x);
    }
}
int get_sum(int x)
{
    int ret=0;
    while(x!=0)
    {
        ret+=c[x];
        x-=lowbit(x);
    }
    return ret;
}

int main()
{
    //数组a元素的初始值为10 20 30 ....100
    for(int i=1; i<=10; i++)
    {
        modify(i,i*10);//modify始终是修改数组a的值
    }
    printf("%d\n",get_sum(10));
    modify(5,-20);
    printf("%d\n",get_sum(10));
    printf("%d\n",get_sum(4));
    return 0;

}

士兵杀敌问题的树状数组解决方案

学习了树状数组,了解了基本性质,下面用树状数组解决士兵杀敌问题2
#include<stdio.h>
#include<string.h>
#define MAX 1000000
int c[MAX]={0};
int a[MAX]={0};
int N,M;
int lowbit(int t)
{
    return t&(-t);
}
void change_val(int i,int n)//树状数组,所有累加项里包含原来a[i]的点都要累加
{
    while(i<=N)
    {
        c[i]+=n;
        i=i+lowbit(i);
    }
}
int sum(int begin,int end)//没有找到直接计算begin--end之间的和的办法,间接的求
{
    int sum1=0,sum2=0;
    while(end>0)
    {
        sum1+=c[end];
        end-=lowbit(end);
    }
    while(begin-1>0)
    {
        sum2+=c[begin];
        begin-=lowbit(begin);
    }
    return sum1-sum2;

}
int main()
{
    int begin,end,i,t;
    char cmd[10];
    freopen("input.txt","r",stdin);
    scanf("%d %d",&N,&M);
    for(i=1;i<=N;i++)
    {
        scanf("%d",&a[i]);
    }
    for(i=1;i<=N;i++)//把数组a的内容转换为树状数组
    {
        t=lowbit(i);
        while(t--)
        {
            c[i]+=a[i-t];
        }
    }
    while(M--)
    {
        scanf("%s %d %d",cmd,&begin,&end);
        if(strcmp(cmd,"QUERY")==0)
        {
            printf("%d\n",sum(begin,end));
        }
        else if(strcmp(cmd,"ADD")==0)
        {
            change_val(begin,end);
        }
    }
    return 0;
}

















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值