- 前言
蓝桥杯用到这部分知识点很少,只是用到最基本的用法。线段树包括树状数组的知识。
树状数组特点:代码短,效率高,可以快速动态的求前缀和
只能求解这一个问题,其他的问题转化为这个问题后才能使用树状数组解决
建议:能用树状数组做的就不用线段树
- 树状数组
定义:
是一个顺序存储的多叉树.
用c[i]\tr[i]表示,树状数组每个元素存储的都是原数组中一段连续区间的和
原理:太复杂,略
时间复杂度:O(log n)
应用:快速动态求前缀和
功能:单点修改、区间求和
和差分比较:都能实现单点修改和区间查询,但是树状数组在动态修改数组并查询区间时更为灵活,差分还能更轻松实现区间修改
操作/函数:(1)给某个位置的数加上一个数 log n
void add(int x)
{
for(int i=x;i<=n;i+=lowbit(i)) c[i]+=x;
}
注意:只依赖有直系血缘关系的当前下标为x的结点的父结点的下标为x+lowbit(x)
(2)求某一个前缀和 log n
int sum(int x)
{
int res=0;
for(int i=x;i>0;i-=lowbit(i)) res+=c[i]
return res;
}
- 线段树
功能:单点修改、区间求和、区间修改(没有懒标记)、求区间最大值
操作:(1)单点修改 log n
(2)区间求和 log n
函数:
(1)pushup 用子节点信息来更新当前节点信息
(2)build 在一段区间上初始化线段树
(3)modify 修改
(4)query 查询
是一个用顺序存储结构存储的二叉树
eg:
acWing1270.动态求连续区间的和
给定 n 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列[a,b] 的连续和。
输入格式
第一行包含两个整数 n 和 m,分别表示数的个数和操作次数。
第二行包含 n 个整数,表示完整数列。
接下来 m 行,每行包含三个整数 k,a,b (k=0,表示求子数列[a,b]的和;k=1,表示第 a 个数加 b)。
数列从 1 开始计数。
输出格式
输出若干行数字,表示 k=0 时,对应的子数列 [a,b][a,b] 的连续和。
数据范围
1≤n≤100000,
1≤m≤100000,
1≤a≤b≤n,
数据保证在任何时候,数列中所有元素之和均在 int 范围内。输入样例:
10 5 1 2 3 4 5 6 7 8 9 10 1 1 5 0 1 3 0 4 8 1 7 5 0 4 8
输出样例:
11 30 35
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=100010; int a[N],tr[N]; int n,m; int lowbit(int x) { return x&(-x); } void add(int x,int y) { for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=y; } int sum(int x) { int res=0; for(int i=x;i;i-=lowbit(i)) res+=tr[i]; return res; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=n;i++) add(i,a[i]); int k,p,q; while(m--) { scanf("%d%d%d",&k,&p,&q); if(k==0) printf("%d\n",sum(q)-sum(p-1)); else add(p,q); } return 0; }
法一:如上,树状数组
法二:线段树
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=100010; int n,m; int w[N]; struct Node { int l,r; int sum; }tr[N*4]; void pushup(int u)//用儿子更新当前节点权值 { tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum; } void build(int u,int l,int r) { if(l==r) tr[u]={l,r,w[r]};//叶子结点 else//非叶子结点 { tr[u]={l,r,0};//一定要赋初值,不然会出错 int mid=l+r>>1; build(u<<1,l,mid),build(u<<1|1,mid+1,r); pushup(u); } } int query(int u,int l,int r) { if(tr[u].l>=l&&tr[u].r<=r) return tr[u].sum;//1.当前区间被完全包括 int mid=tr[u].l+tr[u].r>>1;//注意是tr数组元素的区间中点 int sum=0; if(l<=mid) sum=query(u<<1,l,r);//2.该区间的左半部分被包括 if(r>mid) sum+=query(u<<1|1,l,r);//3.该区间的右半部分被包括 return sum; } void modify(int u,int x,int v) { if(tr[u].l==tr[u].r) tr[u].sum+=v; else { int mid=tr[u].l+tr[u].r>>1; if(x<=mid) modify(u<<1,x,v); else modify(u<<1|1,x,v); pushup(u); } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&w[i]); build(1,1,n); int k,a,b; while(m--) { scanf("%d%d%d",&k,&a,&b); if(k==0) printf("%d\n",query(1,a,b)); else modify(1,a,b); } return 0; }
蓝桥杯:线段树、树状数组
于 2022-02-12 00:32:16 首次发布
本文详细介绍了树状数组和线段树这两种数据结构,重点讨论它们在快速动态求解区间和问题上的应用。树状数组以其简洁的代码和高效的性能,成为解决此类问题的首选。线段树虽然功能更强大,但在特定场景下,树状数组更具优势。通过实例展示了如何使用树状数组和线段树解决动态修改数组并查询区间和的问题,提供了两种不同的实现代码供参考。
摘要由CSDN通过智能技术生成