树状数组基础知识以及相关习题

news2025/1/12 12:08:51

文章目录

  • 什么是树状数组?
  • 如何理解树状数组
  • 如何理解精髓lowbit
  • 二叉树和树状数组的结构
  • 树状数组的优点
  • 树状数组模板
    • 单点修改,区间查询
    • 区间修改,单点查询
    • 区间修改,区间查询
      • 树状数组法
      • 线段树法
  • 树状数组基础练习题
    • 逆序对
    • 动态求连续区间和
    • 数星星
    • 校门外的树
    • 简单题
    • 打鼹鼠

什么是树状数组?

 和并查集一样,树状数组和线段树都是算法竞赛中常见的数据结构,他们通常结构清晰,操作方便,应用灵活,效率很高。

如何理解树状数组

  • 一句话概括:树状数组可以高效率的查询和维护前缀和(或区间和)

 所谓前缀和,即给出长度为n的数列A={ a 1 , a 2 , a 3 , . . . . , a x a_1,a_2,a_3,....,a_x a1,a2,a3,....,ax}和一个查询 x < = n x<=n x<=n,求 s u m ( x ) = a 1 + a 2 + … + a x sum(x)=a_1+a_2+…+a_x sum(x)=a1+a2++ax。数列 [ i , j ] [i,j] [ij]区间和通过前缀和求得,即ai+…+aj=sum(j)-sum(i-1)。
 如果数列A是静态不变的,代码很好写,预处理前缀和就好了,一次预处理的复杂度为0(n),然后每次查询复杂度都为O(1)。但是,如果序列是动态变化的,如改变其中一个元素a的值,那么它后面的前缀和都会改变,需要重新计算,如果每次查询前元素都有变化,那么一次查询的复杂度就变为O(n)。

如何理解精髓lowbit

lowbit(x)=x&-x,功能是找x的二进制最后一个1,原理是用到了负数的补码。下面这两张张表可能能更好的让你理解为什么树状数组这样构造,以及他的基础函数的由来

在这里插入图片描述
在这里插入图片描述

二叉树和树状数组的结构

在这里插入图片描述
上述图片给我们展示了二叉树到树状数组的结构变换,有助于我们更好的理解树状数组。

树状数组的优点

优点:修改和查询操作复杂度于线段树一样都是logN,但是常数比线段树小,并且实现比线段树简单

缺点:扩展性弱,线段树能解决的问题,树状数组不一定能解决.

树状数组模板

万变不离其宗,这两个函数是树状数组的精髓,要好好掌握。

void update (int x,int k) {
	while (x<=N) {
		tree[x]+=k;
		x+=lowbit(x);
	}
}
int query (int x) {
	int ans=0;
	while (x>0) {
		ans+=tree[x];
		x-=lowbit(x);
	}
	return ans;
}

关于如何理解树状数组可以结合有关资料学习,这里只给出模板,并给出一些练习题帮助更好的理解树状数组

单点修改,区间查询

const int N = 1e6+5;
int a[N],b[N];
int tree[N];
int n,m;

void solve ()
{
	cin>>n>>m;
	for (int i=1;i<=n;i++) {
		int x;cin>>x;
		update(i,x);
	}
	for (int i=1;i<=m;i++) {
		int pos,x,y;cin>>pos>>x>>y;
		if(pos==1) {
			update(x,y);
		}
		else 
		{
			cout<<query (y)-query (x-1)<<'\n';
		}
	}
	return ;
}

区间修改,单点查询

区间修改,单点查询的模板用到了差分数组,不懂差分的可以去查询有关资料

const int N = 1e6+5;
int a[N],b[N];
int chafen[N];
int tree[N];
int n,m;

void solve ()
{
	cin>>n>>m;
	for (int i=1;i<=n;i++) {
		cin>>chafen[i];
		int x=chafen[i]-chafen[i-1];
		update(i,x);
	}
	for (int i=1;i<=m;i++) {
		int pos,x,y,k;cin>>pos;
		if(pos==1) {
			cin>>x>>y>>k;
			update(x,k);
			update(y+1,-k);
		}		
		else {
			cin>>x;
			cout<<sum(x)<<'\n';
		}
	}
	return ;
}

区间修改,区间查询

这部分代码有些难以实现,运用到了数学的相互转换。区间修改,区间查询是线段树的强项。我把两种方法都贴出来以供参考

树状数组法

#include <bits/stdc++.h>
#define lowbit(x) (x&(-x))
using namespace std;
const int N = 1e6+5;
int a[N],b[N];
int tree1[N],tree2[N];
int m,n;

void update1 (int x,int k) {
	while (x<=n) {
		tree1[x]+=k;
		x+=lowbit(x);
	}
}

void update2 (int x,int k) {
	while (x<=n) {
		tree2[x]+=k;
		x+=lowbit(x);
	}
}

int sum1 (int x) {
	int res=0;
	while (x>0) {
		res+=tree1[x];
		x-=lowbit(x);
	}
	return res;
}

int sum2 (int x) {
	int res=0;
	while (x>0) {
		res+=tree2[x];
		x-=lowbit(x);
	}
	return res;
}

void solve () {
	cin>>m>>n;
	for (int i=1;i<=m;i++) {
		cin>>a[i];
		int x=a[i]-a[i-1];
		update1 (i,x);
		update2 (i,(i-1)*x);
	}
	for (int i=1;i<=n;i++) {
		int pos;cin>>pos;
		if (pos==1) {
			int x,y,k;
			cin>>x>>y>>k;
			update1(x,k);
			update1(y+1,-k);
			update2(x,k*(x-1));
			update2(y+1,-k*y);
		}
		else {
			int x,y;cin>>x>>y;
			int ans1=y*sum1(y)-sum2(y);
			int ans2=(x-1)*sum1(x-1)-sum2(x-1);
			cout<<ans1-ans2<<'\n';
		}
	}
}

线段树法

#include <bits/stdc++.h>
#define int long long 
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int N = 1e5+5;
int a[N];
int tree [N<<2],tag[N<<2];

int ls (int p) {return p<<1;}
int rs (int p) {return p<<1|1; }

void push_up (int p) {
	tree[p]=tree[ls(p)]+tree[rs(p)];
//	tree[p]=min (tree[ls(p)],tree[rs(p)]);//Çó×îСֵµÄʱºòpush_upº¯ÊýÀïÃæÊÇÕâ¸ö 
}

void build (int p,int pl,int pr) {
	tag[p]=0;
	if (pl==pr) {
		tree[p]=a[pl];return ;
	}
	int mid = (pl+pr)>>1;
	build (ls(p),pl,mid);
	build (rs(p),mid+1,pr);
	push_up(p);
}

void addage (int p,int pl,int pr,int d) {
	tag[p] += d;
	tree[p] += d*(pr-pl+1);
}

void push_down (int p,int pl,int pr) {
	if (tag [p]) {
		int mid = (pl+pr)>>1;
		addage  (ls(p),pl,mid,tag[p]);
		addage (rs(p),mid+1,pr,tag[p]);
		tag[p]=0;
	}
}

void update (int l,int r,int p,int pl,int pr,int d) {
	if (l <= pl && pr <= r) {
		addage (p,pl,pr,d);return ;
	}
	push_down(p,pl,pr);
	int mid=(pl+pr)>>1;
	if (l <= mid) update (l,r,ls(p),pl,mid,d);
	if (r > mid) update (l,r,rs(p),mid+1,pr,d);
	push_up (p);
}

int query (int l,int r,int p,int pl,int pr) {
	if (pl>=l&&pr<=r) return tree[p];
	push_down (p,pl,pr);
	int res=0;
	int mid = (pl+pr)>>1;
	if (l<=mid) res += query (l,r,ls(p),pl,mid);
	if (r>mid) res +=query (l,r,rs(p),mid+1,pr);
	return res; 
}

void solve () {
	int n,m;cin>>n>>m;
	for(int i=1;i<=n;i++)  cin>>a[i];
	build (1,1,n);
	while (m--) {
		int q,l,r,d;cin>>q;
		if (q==1) {
			cin>>l>>r>>d; update (l,r,1,1,n,d);
		}
		else {
			cin>>l>>r;
			cout<<query (l,r,1,1,n)<<'\n';
		}
	}
	
}

虽然说线段树才是这类题的正解,但是线段树代码量很大,难以实现。树状数组相较于它的优点就是代码量少。但是有些问题(当然我还没遇到)只能用线段树来解决,树状数组就显得力不从心了。

树状数组基础练习题

逆序对

来源:洛谷
这是一道利用离散化结合树状数组的例题

 一道逆序对的例题,这个例题可以用归并排序的方法写,也可以用树状数组的方法写,如果要用到树状数组来写的话,就要用到离散化,因为数据比较大。这里我们用到的方法是树状数组。
其中的核心思想就是把数字看成树状数组的下标

#include <bits/stdc++.h>
#define lowbit(x) (x&(-x))
using namespace std;
const int N = 1e6+5;
int a[N];
int chafen[N];
int tree[N];
int n,m;

struct jiegou {
	int x,y;
}b[N];

bool cmp (jiegou qwe,jiegou asd) {
	if (qwe.x!=asd.x)	return qwe.x<asd.x;
	return qwe.y<asd.y;
}

//两个基础函数

void solve () {
	cin>>n;
	for (int i=1;i<=n;i++) {
		cin>>b[i].x;
		b[i].y=i;
	}
	sort (b+1,b+1+n,cmp);
	for (int i=1;i<=n;i++) 
		a[i]=b[i].y;
		
	int res=0;
	for (int i=n;i>=1;i--) {
		update(a[i],1);
		res+=sum(a[i]-1);
	}
	cout<<res;h
	return ;
}

动态求连续区间和

来源:ACwing
模板题,直接套用模板就可以

const int N = 2e5+5;
int a[N],tree[N];int n,k;
//两个基础函数,这里省略没写
void solve () {
	cin>>n>>k;
	for (int i=1;i<=n;i++) {
		cin>>a[i];
		update (i,a[i]);
	}
	for (int i=1;i<=k;i++) {
		int q,x,y;cin>>q>>x>>y;
		if (q==1) {
			update (x,y);
		}
		else {
			cout<<query (y)-query (x-1)<<'\n';
		}
	}
	return;
}

数星星

来源:ACwing
画出图像就能发现,我们只用求前面有多少小于x的数即可。前面有多少个数字,那么这个输入就是几级星星。多次求前缀和,优先想到我们的树状数组。我们用一个ans数组来存放i级星星有多少个。后续直接输出就可以

//两个基础函数没有写出
void solve () {
	int n;cin>>n;
	for (int i=1;i<=n;i++) {
		int x,y;cin>>x>>y;
		x++;//这里注意下标加1,因为树状数组下标不为0
		ans[query (x)]++;//求x前面有多少个数字,再用ans数组存起来
		update (x,1);
	}
	for (int i=0;i<n;i++) cout<<ans[i]<<'\n';
	return;
}

校门外的树

来源:ACwing
每一个 [ l , r ] [l,r] [l,r]里面的树都是一种,给出很多组l,r。问当查询[l,r]时,有多少种树。
其实这一题用到了一个括号序列的思想,我们把 [ l , r ] [l,r] [l,r]中l上放置一个左括号,r上放置一个右括号。在这一个括号里面树都是一种。这样我们只用维护括号的数量,最后查询括号的数量即可。
怎么查询 [ l , r ] [l,r] [l,r]里面有多少种树?我们画图就能发现。我们把r之前的左括号减去i之前的右括号即可。
故用两个树状数组分别维护左右括号的前缀和,更新时记录左右括号数,查询时相减即能得到结果

void solve () {
	int n,k;cin>>n>>k;
	for (int i=1;i<=k;i++) {
		int q,x,y;cin>>q>>x>>y;
		if (q==1) {
			update1 (x,1);
			update2 (y,1);
		}
		else {
			cout<<query1 (y) - query2 (x-1) << '\n';
		}
	}
}

巧妙的将求树的种类转化成求括号的数量

简单题

来源:ACwing
和上一题差不多,但这一题是反转0,1序列。我们依然用括号的思想, [ l , r ] [l,r] [l,r]里反转,我们就在两边加括号,单点查询的时候,我们就查询这个点被多少个括号扩了起来,就反转了几下,然后只能是0,1的缘故,我们直接判断奇偶就行了。
多点修改,单点查询,十分符合树状数组,直接开写。
实际上就是找这一点(包括这一点)之前左括号与右括号的差值

void solve () {
	int n,k;cin>>n>>k;
	for (int i=1;i<=k;i++) {
		int q,x,y;cin>>q;
		if (q==1) {
			cin>>x>>y;
			update1 (x,1);
			update2 (y,1);
		}
		else {
			cin>>x;
			int q = query1 (x) - query1 (x-1);
//			int w = query2 (x) - query2 (x-1); 
			int pos = query1 (x-1) - query2 (x-1);
			pos += q;
			if (pos%2==0) cout<<"0"<<'\n';
			else cout<<"1"<<'\n';
		}
	}
}

打鼹鼠

来源:ACwing
这是一道二维树状数组的练习题,也是属于的一道板子题,可以看一看,就是将一维改成二维。

const int N=1040;
int c[N][N], n;

void update (int x, int y, int k) {
    for(int i=x; i<=N; i+=lowbit(i)){
        for(int j=y; j<=N; j+=lowbit(j)){
            c[i][j] += k;
        }
    }
}

int query (int x, int y){
    int ans = 0;
    for(int i=x; i>=1; i-=lowbit(i))
        for(int j=y; j>=1; j-=lowbit(j))
            ans += c[i][j];
    return ans;
}

    


void solve () {
	cin>>n;
    int xx, yy, x, y, k, op;
    while(cin>>op, op!=3) {
        if(op==1){
            cin>>x>>y>>k;
            update(x+1, y+1, k);
        }
		else{
            cin>>x>>y>>xx>>yy;
            xx++, yy++;
            cout<<query (xx,yy)-query (xx,y)-query (x,yy)+query (x,y)<<'\n';
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1986162.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言进阶版第六课—操作符

文章目录 1. 操作符的分类2. 原码、反码、补码3. 移位操作符4. 位操作符4.1 位操作符详解4.2 练习4.2.1 不能创建临时变量&#xff0c;实现两个整数的交换4.2.2 求一个整数存储在内存中的二进制1的个数4.2.3 判断一个数是不是2的次方数4.2.4 将二进制第n位置0或置1 5. 逗号表达…

git拉代码

第一步 第二步 第三步

Mysql原理与调优-InnoDB页结构

目录 1.绪论 2.Mysql的数据页组成 2.1 基本组成 2.1 文件头 2.2 页头部&#xff08;Page Header&#xff09; 3.3 infimum supremum 3.4 用户记录 和 空闲空间 3.4.1 用户插入数据步骤 3.4.2 删除数据 3.5 页目录 3.5.1 插入时维护槽的步骤 3.5.2 查询元素的步骤 …

为之“毫厘”、追之“千里”,“仅退款”的存废之争

文丨郭梦仪 橘子&#xff08;化名&#xff09;看到了待审核的起诉书&#xff0c;气不打一处来。这次白嫖行为橘子不打算再忍了。 橘子是拼多多商家&#xff0c;经营化妆品公司&#xff0c;5月份&#xff0c;一个顾客收到货4天说用了过敏&#xff0c;直接退款没有退回产品。橘…

Project Euler_Problem 587_Concave Triangle (背包问题)

原问题&#xff1a; 代码&#xff1a; void solve() {ll i, j, k, p, q, u, v, l, r, x, y, z, z1;z 0;double a, b, c;N 1e9;n 250250;p 1e16;z 250;for (i 1; i < n; i) {C[i] M.NT.prime_pow(i, i, z);}A[0] 1;for (i 1; i < n; i) { x C[i];for (j …

潮流时尚,音质在线的悠律ringbuds pro开放式耳机体验评测

选购耳机时你最看重的要素是什么&#xff1f;对笔者来说&#xff0c;“音质”绝对排在了第一位。然而&#xff0c;当前市场上多数开放式蓝牙耳机音质和舒适性欠佳&#xff0c;让人难以抉择。最近悠律声学品牌推出全新的 OWS 蓝牙耳机ringbuds pro&#xff0c;它以卓越音质、时尚…

streamlit (python构建web)之环境搭建

目录 前言 1. 什么是Streamlit&#xff1f; 2. Streamlit的原理 2.1 demo1-运行计时程序 2.2 demo2-随即密码生成器 3. 安装Streamlit 3.1 安装方式一 3.2 安装方式二 3.2.1 安装 conda 3.2.2 新建一个 conda 环境 3.2.3 激活 conda 环境 3.2.4 安装 Streamlit 库 …

微信小程序教程011-2:京西购物商城实战之TabBar实现

2、tabBar 2.0 创建tabBar分支 运行如下命令,基于master分支,创建本地tabBar子分支,用来开发和tabBar相关的功能 git checkout -b tabbar2.1 创建tabBar页面 在pages目录中,创建首页(home)、分类(cate)、购物车(cart)、我的(my)这4个tabBar页面,在HBuilderX中…

PADS文件与AD文件如何相互转换?

大家好&#xff0c;我是山羊君Goat。 在电子硬件设计中&#xff0c;常常用用到3款EDA设计软件&#xff1a;Altium Designer, Pads, cadence。 Altium Designer: 在高校中使用非常普遍&#xff08;相信很多硬件工程师接触的第一款设计软件就是Altium Designer&#xff09;&…

Python 用户输入和while循环

1、input()函数的工作原理&#xff08;用户输入&#xff09; input()函数让程序暂停运行&#xff0c;等待用户输入一些文本。获取用户输入后&#xff0c;Python将其赋给变量&#xff0c;以其使用。 在input()函数接受一个参数&#xff0c;既要向用户显示的提示&#xff0c;为了…

Flink 实时数仓(八)【DWS 层搭建(二)流量域、用户域、交易域搭建】

前言 今天的任务是完成流量域最后一个需求、用户域的两个需求以及交易域的部分需求&#xff1b; 1、流量域页面浏览各窗口汇总表 任务&#xff1a;从 Kafka 页面日志主题读取数据&#xff0c;统计当日的首页和商品详情页独立访客数。 注意&#xff1a;一般我们谈到访客&…

广东省造林绿化施工丙级资质2024年9月开通申报

关于广东省造林绿化施工丙级资质2024年9月的申报情况&#xff0c;可以归纳如下&#xff1a; 一、申报时间 具体时间&#xff1a;2024年9月1日至9月30日。在此期间&#xff0c;相关企业可以在网上提交申请。 二、申报条件 资历和信誉 1、独立企事业法人资格&#xff1a;申请…

程序跟随系统主题色切换主题

如果程序要跟随系统主题色进行切换&#xff0c;需监听当前系统的主题色&#xff0c;下面介绍Windows和MacOS下获取当前系统主题的方法 Windows 系统切换主题 以win10为例&#xff0c;点击右键选择个性化&#xff0c;进入个性化页面&#xff0c;选择左侧颜色的Tab&#xff0c…

mp3格式转换器哪个好用?汇总七款音频格式转换方法(无损转换)

音乐已经成为我们生活中不可或缺的一部分。但是在播放的时候&#xff0c;可能会遇到音频格式不兼容的情况。特别是在一些下载站或音乐平台获取的音频&#xff0c;有些特殊格式在播放器上无法正常播放&#xff0c;一般这种情况我们需要借助mp3转换器解决。 mp3是一种常见的数字音…

三更的springsecurity课程个人笔记总计4万字,全部测试通过,代码cv即可

SpringSecurity b站 40.源码讲解部分说明_哔哩哔哩_bilibili BV1mm4y1X7Hc 以下全为个人总结&#xff0c;不能代表官方&#xff0c;有错误还请指出&#xff08;全部测试通过&#xff09;&#xff08;1刷视频&#xff09; 1-简介 tip 接下来的所有类不会包含import信息&am…

爬虫代理教程:爬虫代理池部署+高并发实现方法

在数据爬取的世界里&#xff0c;代理IP就像是爬虫的隐身衣&#xff0c;帮助我们在网络上自由穿梭&#xff0c;避免被目标网站识别封禁。今天我就来分享一下爬虫代理池的部署和高并发实现的技巧&#xff0c;希望能对大家有所帮助。 什么是爬虫代理池&#xff1f; 首先&#xf…

nginx下载安装及使用教程

一、打开下载官网&#xff1a;nginx 选择稳定版本&#xff08;windows&#xff09; 然后就是解压安装到指定目录下 二、启动nginx 使用cmd命令提示符进入&#xff0c;输入一下命令(注意&#xff1a;回车确认是会出现一闪&#xff0c;这是正常现象&#xff09; 查看任务进程是否…

Flink学习之Flink SQL(补)

Flink SQL 1、SQL客户端 1.1 基本使用 启动yarn-session yarn-session.sh -d启动Flink SQL客户端 sql-client.sh--退出客户端 exit;测试 重启SQL客户端之后&#xff0c;需要重新建表 -- 构建Kafka Source -- 无界流 drop table if exists students_kafka_source; CREATE TABL…

SourceTree配置多个不同Remote地址的仓库

需求 在我们开发过程中&#xff0c;有可能需要拉取的地址仓库不在同一个仓库中&#xff0c;有些可能在Github上&#xff0c;有些可能在Gitlab上。 所以我们需要配置Github的仓库的配置和Gitlab仓库的配置。 现在&#xff0c;我们来配置两个不同的仓库的地址。 假设&#xf…

快速体验LLaMA-Factory 私有化部署和高效微调Llama3模型FAQ

序言 之前已经介绍了在超算互联网平台SCNet上使用异构加速卡AI 显存64GB PCIE&#xff0c;私有化部署Llama3模型&#xff0c;并对 Llama3-8B-Instruct 模型进行 LoRA 微调、推理和合并 &#xff0c;详细内容请参考另一篇博客&#xff1a;快速体验LLaMA-Factory 私有化部署和高…