编程学习笔记12--树状数组的运用
2014-12-21 21:00
281 查看
实例问题
士兵杀敌(一)
时间限制: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
9
每次都累加,求范围的时候用末减去初就可以了。
#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-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; }
相关文章推荐
- js学习12-《JS DOM 编程艺术》笔记
- Scala中链式调用风格的实现代码实战及其在Spark编程中的广泛运用之Scala学习笔记-41
- UNIX环境编程学习笔记(12)——文件I/O之目录操作
- 学习笔记-基础知识12-网络编程
- Scala学习笔记12 - GUI编程
- python 学习笔记12-----网络编程
- 第51讲:Scala中链式调用风格的实现代码实战及其在Spark编程中的广泛运用学习笔记
- solr7.1.0学习笔记(12)---Solr客户端SolrJ的运用
- UNIX环境编程学习笔记(12)——文件I/O之目录操作
- 编程学习笔记13--字典树及其运用
- c++ 学习笔记(高级linux编程) day12
- ASP.NET 3.5核心编程学习笔记(12):SqlCommand、SqlDataReader、事务
- UNIX环境高级编程学习笔记:9_12 孤儿进程 孤儿进程组
- JAVA编程思想学习笔记
- 对Outlook 编程的学习笔记
- C专家编程--学习笔记(2)_对内存的思考
- 孙鑫VC学习笔记:第十四讲 网络编程
- [ASP.NET学习笔记之八]ASP.NET控件编程
- Chap 12 学习笔记-从数据源中读取
- Oracle专家高级编程学习笔记( 二)