蓝桥杯:线段树、树状数组

本文详细介绍了树状数组和线段树这两种数据结构,重点讨论它们在快速动态求解区间和问题上的应用。树状数组以其简洁的代码和高效的性能,成为解决此类问题的首选。线段树虽然功能更强大,但在特定场景下,树状数组更具优势。通过实例展示了如何使用树状数组和线段树解决动态修改数组并查询区间和的问题,提供了两种不同的实现代码供参考。
摘要由CSDN通过智能技术生成
  • 前言

蓝桥杯用到这部分知识点很少,只是用到最基本的用法。线段树包括树状数组的知识。

树状数组特点:代码短,效率高,可以快速动态的求前缀和

只能求解这一个问题,其他的问题转化为这个问题后才能使用树状数组解决

建议:能用树状数组做的就不用线段树

  • 树状数组

定义:

是一个顺序存储的多叉树.

用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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值