【C++】树状数组的使用、原理、封装类、样例

前言

本博文代码打包下载
C++算法与数据结构分类汇总

树状数组的英文名称是 ‌Binary Indexed Tree‌,缩写为 ‌BIT‌。我习惯翻译成TreeArr。

最常见的应用

有序集合包括若干整数,求小于x的数量。auto it = s.lower(x) , it - s.begin(),这个时间复杂度是O(n)。
由于查询和插入交替进行,故不能用向量。

树状数组的用途

令原始数组是a,长度为n。

基础操作

一,求前缀和。即 ∑ j : 0 i a [ j ] \sum_{j:0}^ia[j] j:0ia[j]时间复杂度:O(logn)。
二,a[i] +=x。时间复杂度:O(logn)

组合操作

区间和:两个前缀和相减便是区间和,a[i…j]之和=a[0…j]之和-a[0…i-1]之和。
求a[i]的值:即区间a[i…i]之和。
a[i]=x: y=a[i]的值,a[i] += (x-y)

树状数组的原理

性质一,下标从1开始。
性质二,f(x)= x&(x-1) ,即将最低位的1变成0,f(6)=4。data[i]=a[f(i) + 1] 到a[i]之和。故a[1…i] 之和等于按以下方式迭代的a[i]之和:i = f(i)。
性质三,g(i)=x&(-x)即最低位1的值,f(6)=2。a[i]+= x,等于data[i]+=x,按如下方式迭代i: i +g(x)。
性质四:f(i)最低位1后面的任意一个0变成1,一定是对应i。且不存在其它方式产生对应的i。
性质五:f(j) < i <= j, f(j)中1的个数不会多于f(i)。如果f(j)小于f(i),根据性质四无法让j >=i。令f(j)的第k3位小于f(i),如果有多个k3,取最高位。不修改k3或更高位,无法大于等于i。
如果f(j)>f(i),说明i的最低位1后面有1,根据性质四,无法如何j都不会大于i。
令i的最后一个1是第k4位,只讨论比k4高的位,f(j)<=i,否则整个f(j)>i。比k4高的位f(i)等于i,故f(j)和f(i)k4为之前部分相等。f(j)>f(i),故必须比k4低的位有1。
性质六:如果f(j)有k个1,这k个1一定是f(i)的前k个1。证明同性质五。
如果f(j)有k个1,i从高位到低位第k个1是k1位,第k+1个1是k2位。 如果i有k+1个1.则j最后一个1为[k2,k1 - 1];否则j最后一个1为[k2 + 1, k1 - 1]。证明结束。

树状数组的封装类

静态开点求和和求异或和的树状数组。

template<class ELE = int >
class CTreeArrAddOpe:public ITreeArrSumOpe<ELE>
{
public:
	virtual void Assign(ELE& dest, const ELE& src) {
		dest += src;
	}
	virtual ELE Back(const ELE& n1, const ELE& n2) {
		return n1 - n2;
	}
};

template<class ELE = int,class ELEOpe = CTreeArrAddOpe<ELE> >
class CTreeArr
{
public:
	CTreeArr(int iSize) :m_vData(iSize + 1)
	{

	}
	void Add(int index, ELE value)
	{
		if ((index < 0) || (index >= m_vData.size() - 1)) { return; }
		index++;
		while (index < m_vData.size())
		{
			m_ope.Assign(m_vData[index], value);
			index += index & (-index);
		}
	}
	ELE Sum(int index)//[0...index]之和
	{
		index++;
		ELE ret = 0;
		while (index)
		{
			m_ope.Assign(ret, m_vData[index]);
			index -= index & (-index);
		}
		return ret;
	}
	ELE Sum() { return Sum(m_vData.size() - 2); }
	ELE Get(int index)
	{
		return m_ope.Back(Sum(index), Sum(index - 1));
	}	
private:
	ELEOpe m_ope;
	vector<ELE> m_vData;
};

template<class ELE = int >
class CTreeArrXorOpe : public ITreeArrSumOpe <ELE> {
public:
	virtual void Assign(ELE& dest, const ELE& src) {
		dest ^= src;
	}
	virtual ELE Back(const ELE& n1, const ELE& n2) {
		return n1 ^ n2;
	}
};

template<class ELE = int >
class CTreeArrXor : public CTreeArr<ELE, CTreeArrXorOpe<ELE>> {
public:
	using CTreeArr<ELE, CTreeArrXorOpe<ELE>>::CTreeArr;
};

动态开点树状数组

template<class ELE = int, class ELEOpe = CTreeArrAddOpe<ELE> >
class CTreeArrMap
{
public:
	CTreeArrMap(long long llMin, long long llMax) :m_llMin(llMin), m_llMax(llMax) {}
	void Add(long long index, ELE value)
	{
		if ((index < m_llMin) || (index > m_llMax)) { return; }
		index = index - m_llMin + 1;
		auto maxIndex = m_llMax - m_llMin + 1;
		while (index <= maxIndex)
		{
			m_ope.Assign(m_vData[index], value);
			index += index & (-index);
		}
	}
	ELE Sum(long long index)//[0...index]之和
	{
		if ((index < m_llMin) || (index > m_llMax)) { return 0; }
		index = index - m_llMin + 1;
		ELE ret = 0;
		while (index)
		{
			m_ope.Assign(ret, m_vData[index]);
			index -= index & (-index);
		}
		return ret;
	}
	ELE Sum() { return Sum(m_llMax - m_llMin + 1); }
	ELE Get(long long index)
	{
		return m_ope.Back(Sum(index), Sum(index - 1));
	}
private:
	ELEOpe m_ope;
	unordered_map <long long, ELE> m_vData;
	const long long m_llMin, m_llMax;
};

最值树状数组

template<class T = int,T def = INT_MIN>
class CTreeArrMax
{
public:
	CTreeArrMax(int iEleSize) :m_iMax(iEleSize) {
		m_aMax.assign(iEleSize + 1, def);
		m_aRangMax.assign(iEleSize + 1, def);
	}
	void Modify(int indexBase0, T value)
	{
		indexBase0++;
		if (value <= m_aMax[indexBase0])
		{
			return;
		}
		m_aMax[indexBase0] = value;
		while (indexBase0 <= m_iMax)
		{
			m_aRangMax[indexBase0] = max(m_aRangMax[indexBase0], value);
			indexBase0 += BitLower(indexBase0);
		}
	}
	T Query(int leftBas0, int rBase0)
	{
		leftBas0++;
		rBase0++;
		leftBas0 = max(1, leftBas0);
		rBase0 = min(m_iMax, rBase0);
		T iMax = def;
		while (rBase0 >= leftBas0)
		{
			const int pre = rBase0 - BitLower(rBase0);
			if (pre + 1 >= leftBas0)
			{
				iMax = max(iMax, m_aRangMax[rBase0]);
				rBase0 = pre;
			}
			else
			{
				iMax = max(iMax, m_aMax[rBase0]);
				rBase0--;
			}
		}
		return iMax;
	}
protected:
	int BitLower(int x)
	{
		return x & (-x);
	}
	const int m_iMax;
	vector<T> m_aMax, m_aRangMax;
};

template<class T = int, T def = INT_MAX>
class CTreeArrMin
{
public:
	CTreeArrMin(int iEleSize):m_max(iEleSize){}
	void Modify(int indexBase0, T value)
	{
		m_max.Modify(indexBase0, -value);
	}
	T Query(int leftBas0, int rBase0)
	{
		return -m_max.Query(leftBas0, rBase0);
	}
	CTreeArrMax<T, -def> m_max;
};

typedef CTreeArrMin<long long, LLONG_MAX> CTreeArrLLMin;

和差分数组结合

可以在O(logn)的时间内进行区间修改,在O(logn)的时间内进行单点查询。

树状数组使用样例

力扣

难度分
【C++二分查找 树状数组】2424. 最长上传前缀1604
【C++ 树状数组】2426. 满足不等式的数对数目2030
【C++ 树状数组 】1850. 邻位交换的最小次数2073
【字符串】【贪心】【 树状数组】2193. 得到回文串的最少操作次数2090
【C++树状数组】3187. 数组中的峰值2154
【树状数组】1649. 通过指令创建有序数组2207
【树状数组 一一映射】2179. 统计数组中好三元组数目2272
【状态机dp 线段树 树状数组】2407. 最长递增子序列 II2280
【树状数组】2659. 将数组清空2281
【树状数组 队列】1505. 最多 K 次交换相邻数位后得到的最小整数2336

洛谷

难度等级
【树状数组 C++二分查找】P7333 [JRKSJ R1] JFCA普及+
【前缀和 前后缀分解 树状数组】P9744 「KDOI-06-S」消除序列普及+
【C++贪心 树状数组】P8818 [CSP-S 2022] 策略游戏普及+
【树状数组 二分】P10235 [yLCPC2024] C. 舞萌基本练习普及+
【树状数组】P5367康托展开普及+
【树状数组 贪心】P11453 [USACO24DEC] Deforestation S普及+
【树状数组】P10673 【MX-S1-T2】催化剂普及+
【树状数组 排序 双指针】P11048 [蓝桥杯 2024 省 Java B] 拼十字普及+
【树状数组】P6278 [USACO20OPEN] Haircut G普及+
【树状数组】P3608 [USACO17JAN] Balanced Photo G普及+
【树状数组】P5200 [USACO19JAN] Sleepy Cow Sorting G普及+
【树状数组 位运算】P6225 [eJOI 2019] 异或橙子普及+
【树状数组】P5459 [BJOI2016] 回转寿司普及+
【树状数组】P5094 [USACO04OPEN] MooFest G 加强版普及+
【差分数组 树状数组】P5057 [CQOI2006] 简单题普及+
【树状数组 】P7629 [COCI 2011/2012 #1] SORT普及+

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步优快云学院,听白银讲师(也就是鄙人)的讲解。
https://edu.youkuaiyun.com/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.youkuaiyun.com/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

<think>好的,我现在需要帮助用户查找蓝桥杯比赛中关于棋盘问题的树状数组解法或实现代码。首先,我要确定用户的需求是什么。用户提到了“棋盘问题”树状数组”,可能需要解决的是二维区间操作问题,比如格子修改或查询,这在蓝桥杯的题目中比较常见,比如《棋盘上的问题》或《格子刷油漆》等题目。 首先,我需要回忆树状数组(Fenwick Tree)的基本知识。树状数组通常用于高效处理前缀查询单点更新,时间复杂度为O(log n)。但在棋盘问题中,可能涉及到二维的情况,这时候需要使用二维树状数组,可以处理二维区域的操作,比如子矩阵的更新求和。 接下来,我需要考虑蓝桥杯历届真题中是否有类似的题目。根据引用[1][2],用户提供的参考资料可能涉及C++Python的代码,但具体到棋盘树状数组的结合,可能需要进一步查找相关题解。如,有些题目可能要求动态维护棋盘的状态,进行多次区间增减操作,并查询某个区域的或极。这时候树状数组的优势就体现出来了。 然后,用户需要解题思路实现代码。针对二维棋盘问题,树状数组的二维实现是关键。每个节点(i,j)维护的是一个二维的前缀信息。当进行区间更新时,可能需要使用差分的方法,将区间操作转化为四个单点操作,再利用树状数组的高效更新查询。 如,假设题目要求对棋盘的一个子矩阵进行增加某个,然后查询某个点的。这时候可以用二维树状数组结合差分的方法。具体来说,对于每个更新操作(x1,y1,x2,y2)加上v,可以通过四次更新操作实现:在(x1,y1)加v,(x1,y2+1)减v,(x2+1,y1)减v,(x2+1,y2+1)加v。这,查询某个点(x,y)的时,只需计算前缀即可得到当前。 接着,代码实现部分需要构建二维树状数组的结构。在Python中,可以初始化一个二维数组,并实现updatequery方法。需要注意的是,树状数组的下标通常从1开始,因此在处理输入数据时可能需要调整坐标。 验证思路的正确性也很重要。如,考虑一个小棋盘,手动模拟几次更新查询操作,确保结果符合预期。同时,需要注意边界条件,比如当更新区域超出棋盘范围时的处理。 后,根据用户的引用资料,引用[1]提到了C++中的unordered_map用于节点答案的存储,可能与本题无关,但引用[2]中的Python处理大数的方法可能在输入时有用,但树状数组的实现本身不需要处理大数,所以主要关注树状数组的结构操作。 总结步骤: 1. 确定问题类型:二维区间更新单点查询。 2. 使用二维树状数组结合差分方法处理区间更新。 3. 实现二维树状数组的updatequery函数。 4. 编写代码框架,处理输入输出,并测试。 可能的挑战包括正确实现二维树状数组的更新查询逻辑,以及差分方法的正确应用。需要仔细检查每个步骤,避免逻辑错误。</think>关于蓝桥杯比赛中棋盘问题与树状数组的结合应用,以下为典型解题思路及实现代码: --- ### 问题特征 棋盘问题通常涉及**二维区间操作**,如: - 多次对子矩阵执行增减操作 - 查询单点或子矩阵的 - 统计符合特定条件的区域 树状数组(Fenwick Tree)通过$O(\log n)$时间复杂度实现高效的前缀计算与单点更新,适合处理这类高频操作的问题[^1]。 --- ### 解题思路(以子矩阵增减+单点查询为) 1. **差分思想** 将二维区间增减转化为四个单点操作: $$ \text{update}(x1,y1,+v),\ \text{update}(x1,y2+1,-v) $$ $$ \text{update}(x2+1,y1,-v),\ \text{update}(x2+1,y2+1,+v) $$ 2. **二维树状数组实现** 维护差分数组的前缀,实现单点修改与区域查询: ```python class Fenwick2D: def __init__(self, n, m): self.n = n self.m = m self.tree = [[0]*(m+1) for _ in range(n+1)] def update(self, x, y, delta): while x <= self.n: y1 = y while y1 <= self.m: self.tree[x][y1] += delta y1 += y1 & -y1 x += x & -x def query(self, x, y): res = 0 while x > 0: y1 = y while y1 > 0: res += self.tree[x][y1] y1 -= y1 & -y1 x -= x & -x return res ``` 3. **操作映射** - **子矩阵更新**:调用四次`update()`实现差分 - **单点查询**:直接计算`query(x,y)` --- ### 完整代码实现(Python) ```python class Fenwick2D: def __init__(self, n, m): self.n = n self.m = m self.tree = [[0]*(m+2) for _ in range(n+2)] # 下标从1开始 def update(self, x, y, delta): while x <= self.n: y1 = y while y1 <= self.m: self.tree[x][y1] += delta y1 += y1 & -y1 x += x & -x def query(self, x, y): res = 0 while x > 0: y1 = y while y1 > 0: res += self.tree[x][y1] y1 -= y1 & -y1 x -= x & -x return res # 输入棋盘大小n x m,操作次数q n, m = map(int, input().split()) q = int(input()) fw = Fenwick2D(n, m) for _ in range(q): op = list(map(int, input().split())) if op[0] == 1: # 子矩阵更新: op1 x1 y1 x2 y2 v x1, y1, x2, y2, v = op[1], op[2], op[3], op[4], op[5] fw.update(x1, y1, v) fw.update(x1, y2+1, -v) fw.update(x2+1, y1, -v) fw.update(x2+1, y2+1, v) else: # 单点查询: op2 x y x, y = op[1], op[2] print(fw.query(x, y)) ``` --- ### 应用场景 1. 蓝桥杯《格子刷油漆》题型中动态维护棋盘状态 2. 统计符合条件(如异或特征)的区域数量 3. 解决多次操作后的棋盘极问题 ---
评论 69
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

软件架构师何志丹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值