线段树和树状数组,是两个十分相似的数据结构。他们能使对一个区间的数修改以及查询的速度提升许多。两个结构本质相同,各有优缺点。
线段树
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。
比如讲一个有4个数的线段树,是长这个样子的:
一号节点,代表着区间1~4
二号节点,代表区间1~2
三号节点,代表区间3~4
以此类推。。。。。。
很容易发现,对于n号节点来说,n×2代表着它的区间的前半段,n×2+1代表着它的区间的后半段。
树状数组
树状数组是一个很奇特的树,它的节点会比线段树少一些,也能表示一个数组。
c数组就是树状数组,能看出来。
c1=a1;
c2=a1+a2;
c3=a3;
c4=a1+a2+a3+a4;
以此类推。。。。。。 很难说出他们的关系,但是如果把它们变为二进制:
c0001=a0001
c0010=a0001+a0010
c0011=a0011
c0100=a0001+a0010+a0011+a0100
你会发现,将每一个二进制,去掉所有高位1,只留下最低位的1,然后从那个数一直加到1。
总结
时间复杂度
虽然它们都是nlogn,但是,你会发现,在查询时,树状数组最坏情况是logn(比如8个数,然后查询8),但是线段树是所有情况都是nlogn,稍慢于树状数组。
空间复杂度
树状数组完胜于线段树,线段树要开2倍到4倍内存(推荐4倍),但是树状数组一倍就够了。
适用范围
线段树之所以存在的理由是因为它能适用于很多方面,不仅仅是区间、单点的查询修改,还有标记等等,可以用于模拟、DP等等,而且空间经过离散化以后也可以相对压缩,所以适用范围线段树更加广一些。
详细讲解
原题地址
题目描述
给定一个长度为n(n<=100000),初始值都为0的序列,x(x<=10000)次的修改某些位置上的数字,每次加上一个数,然后提出y (y<=10000)个问题,求每段区间的和。时间限制1秒。
输入输出格式
输入格式:
第一行1个数,表示序列的长度n
第二行1个数,表示操作的次数w
后面依次是w行,分别表示加入和询问操作
其中,加入用x表示,询问用y表示
x的格式为"x a b" 表示在序列a的位置加上b
y的格式为"y a b" 表示询问a到b区间的加和
输出格式:
每行一个数,分别是每次询问的结果
输入输出样例
输入样例#1:
5
4
x 3 8
y 1 3
x 4 9
y 3 4
输出样例#1:
8
17
代码:
树状数组做法:
#include
#include
#include
#include
using namespace std;
int tree[100010],n,m;
int lowbit(int x)
{
return x&(-x);//能整出这个数的2的幂次的最大幂
}
void add(int p,int x)//建立线段树
{
while(p<=n)
{
tree[p]+=x;
p+=lowbit(p);
}
}
int ask(int p)//查询
{
int ans=0;
while(p>0)
{
ans+=tree[p];
p-=lowbit(p);
}
return ans;
}
int main()
{
int i,a,b,q;
char k;
cin>>n>>m;
memset(tree,0,sizeof(tree));
for(i=1;i<=m;i++)
{
cin>>k>>a>>b;
if(k=='x') add(a,b);
if(k=='y')
{
q=ask(b)-ask(a-1);
printf("%d\n",q);
}
}
return 0;
}
原题地址
题目背景
很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。这让很多学生很反感。
题目描述
不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩
输入输出格式
输入格式: 输出格式: 输入输出样例 输入样例#1: 代码: 然而,对于一些较为简单的题目,如果查询并不复杂,可以使用差分的方法。 原题地址 题目描述 语文老师总是写错成绩,所以当她修改成绩的时候,总是累得不行。她总是要一遍遍地给某些同学增加分数,又要注意最低分是多少。你能帮帮她吗? 输入输出格式 输入格式: 第二行有n个数,a1~an,代表各个学生的初始成绩。 接下来p行,每行有三个数,x,y,z,代表给第x个到第y个学生每人增加z分。 输出格式: 输入输出样例 输入样例#1: 对于40%的数据,有n<=1000 对于60%的数据,有n<=10000 对于80%的数据,有n<=100000 对于100%的数据,有n<=5000000,p<=n,学生初始成绩<=100,z<=100 代码: 于是自然而然想到差分了,这样只需要O(n)的时间复杂度就可以解决这道题了。 AC代码: 原题地址 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.求出某区间每一个数的和 输入输出格式 输入格式: 第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。 接下来M行每行包含3或4个整数,表示一个操作,具体如下: 操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k 操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和 输出格式: 输入输出样例 输入样例#1: 时空限制:1000ms,128M 数据规模: 对于30%的数据:N<=8,M<=10 对于70%的数据:N<=1000,M<=10000 对于100%的数据:N<=100000,M<=100000 (数据已经过加强_,保证在int64/long long数据范围内) 代码:
第一行,有两个正整数 N 和 M ( 0
对于每一次询问操作,在一行里面输出最高成绩
5 6
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 2 9
Q 1 5
输出样例#1:
5
6
5
9#include
差分
例题:P2367 语文成绩
第一行有两个整数n,p,代表学生数与增加分数的次数。
输出仅一行,代表更改分数后,全班的最低分。
3 2
1 1 1
1 2 1
2 3 1
输出样例#1:
2
说明
区间修改?乍一看似乎要用线段树才能解决。不过读完题目,就会发现只需要在最后求出最小值,并不需要线段树这样的在线算法。#include
线段树模板
例题:P3372 【模板】线段树 1
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。
输出包含若干行整数,即为所有操作2的结果。
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
输出样例#1:
11
8
20
说明
lazy_tag的思想是,我们把一个区间拆成多个区间,每个区间打上一个标记,标记它的叶子节点要加上多少,它自己的值可以在O(1)的时间内算出,等到我们要使用它的儿子时,再把标记下压到它的儿子上 首先,我们需要一个更新自己的push_up()函数
然后,我们需要一个push_down()函数,用于下压标记。
由于博主太懒,详细讲解请转至详细讲解#include