【黑科技】用树状数组解决区间修改查询问题

本文介绍使用树状数组处理区间查询与修改的方法,并详细解释了一维与多维(特别是二维)情况下具体实现细节,包括关键函数Gans的运作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

遇到区间查询修改,相信大家一般用的都是线段树,
但常数远远没有树状数组优秀,
这里就来谈谈如何用树状数组解决区间问题
(同时可以用于多维问题)


首先,相信大家一定会用树状数组做区间加,单点查询的问题,
差分一下即可;

那么对于区间查询,怎么做呢?
要做到区间查询,就要能计算一个点的值对后面的值的影响,
多开一个数组F[i]=(n-i+1)*f[i],(f为用来差分的数组)
差分的话当然是这个点的值对后面的每个点都有影响,所以权值乘上那么多,
但很显然,在计算区间是,它的影响没有那么大,
设查询的区间是l,r,那么它在答案中应该是:(r−i+1)∗f[i]=F[i]−(n−r)∗f[i](r-i+1)*f[i]=F[i]-(n-r)*f[i](ri+1)f[i]=F[i](nr)f[i]
发现(n-r)对于任意的f[i]都是一定的,也就是可以区间求和以后直接乘上(n-r)减掉F多出来的部分,再加上l之前的影响,

这样既可实现用树状数组解决区间修改查询问题,

那么现在问题来了,如何用树状数组实现二维或多维的求和,
以二维为例,
对平面上的一个矩形加/减,求矩形里点的和,
我们需要多开3个数组,
F1[i][j]=(m-j+1)*(n-i+1)*f[i],
F2[i][j]=(n-i+1)*f[i],
F3[i][j]=(m-j+1)*f[i],
用矩阵和的套路,减掉右边左边,加上右下角的,即可,
对于前面的影响,与处理后面的差不多,

(这个需要大家自己感受一下,实在不会就看标吧)
(重点在Gans这个函数)

void change(int x,int y,int e)
{
	if(x>n||y>m)return;
	for(int i=x;i<=n;i+=NX(i))
		for(int j=y;j<=m;j+=NX(j))
		{
			f[i][j]+=e;
			f1[i][j]=(f1[i][j]+e*(n-x+1)*(m-y+1))%mo;
			f2[i][j]=(f2[i][j]+e*(n-x+1))%mo;
			f3[i][j]=(f3[i][j]+e*(m-y+1))%mo;
		}
}
void find(int x,int y)
{
	fd1=fd2=fd3=fd=0;
	for(int i=x;i;i-=NX(i))
		for(int j=y;j;j-=NX(j))
		{
			fd+=f[i][j];
			fd1=(fd1+f1[i][j])%mo;
			fd2=(fd2+f2[i][j])%mo;
			fd3=(fd3+f3[i][j])%mo;
		}
}
void add(int x,int y,int x1,int y1)
{
	change(x,y,1);
	change(x,y1+1,-1);
	change(x1+1,y,-1);
	change(x1+1,y1+1,1);
}
int Gans(int x,int y,int x1,int y1)
{
	LL s,s1,s2,s3;
	find(x-1,y1);s=fd,s1=fd1,s2=fd2,s3=fd3;
	find(x-1,y-1);s-=fd,s1-=fd1,s2-=fd2,s3-=fd3;
	LL t=0;
	t=(s3-s*(m-y1))%mo*(x1-x+1)%mo;
	find(x1,y-1);s=fd,s1=fd1,s2=fd2,s3=fd3;
	find(x-1,y-1);s-=fd,s1-=fd1,s2-=fd2,s3-=fd3;
	t=(t+(s2-s*(n-x1))%mo*((y1-y+1))%mo)%mo;
	find(x-1,y-1);s=fd,s1=fd1,s2=fd2,s3=fd3;
	t=(t+s*(y1-y+1)%mo*(x1-x+1))%mo;//这是前面的影响

	find(x1,y1);s=fd,s1=fd1,s2=fd2,s3=fd3;
	find(x-1,y1);s-=fd,s1-=fd1,s2-=fd2,s3-=fd3;
	find(x1,y-1);s-=fd,s1-=fd1,s2-=fd2,s3-=fd3;
	find(x-1,y-1);s+=fd,s1+=fd1,s2+=fd2,s3+=fd3;
	s1=s1-s3*(n-x1)-s2*(m-y1);s1=(s1%mo+mo)%mo;
	s=s%mo;
	s1=(s1+(n-x1)*(m-y1)%mo*s)%mo;
	return (s1+t)%mo;
}
### C++ 中树状数组实现区间修改区间查询 #### 基本原理 树状数组是一种高效处理动态前缀和问题的数据结构。对于 **区间修改** 和 **区间查询** 的需求,可以通过差分数组的思想来解决。 假设原始数组为 `a`,其对应的差分数组为 `d`,其中 \( d_i = a_i - a_{i-1} \)[^3]。通过维护这个差分数组,可以将区间修改转化为单点修改,并利用树状数组完成高效的查询操作。 以下是完整的实现代码: ```cpp #include <bits/stdc++.h> using namespace std; #define lowbit(x) (x & (-x)) const int MAXN = 1e5 + 5; long long c[MAXN], orig_a[MAXN]; int n; // 更新函数:对差分数组进行单点修改 void update(int idx, long long delta) { while (idx <= n) { c[idx] += delta; idx += lowbit(idx); } } // 查询函数:计算前缀和 long long query(int idx) { long long res = 0; while (idx > 0) { res += c[idx]; idx -= lowbit(idx); } return res; } // 初始化树状数组 void init(vector<long long> &arr) { memset(c, 0, sizeof(c)); n = arr.size(); for (int i = 1; i <= n; ++i) { update(i, arr[i]); } } // 区间修改 [l, r] 加上 val void range_update(int l, int r, long long val) { update(l, val); // 对位置 l 进行增加 if (r + 1 <= n) update(r + 1, -val); // 对位置 r+1 进行减少 } // 区间查询 [l, r] long long range_query(int l, int r) { return query(r) - query(l - 1); } int main() { ios::sync_with_stdio(false); cin.tie(0); int m; cin >> n >> m; // 数组长度 n 和 操作次数 m vector<long long> arr(n + 1, 0); for (int i = 1; i <= n; ++i) cin >> arr[i]; init(arr); while (m--) { char op; cin >> op; if (op == 'Q') { // Query int l, r; cin >> l >> r; cout << range_query(l, r) << "\n"; } else if (op == 'U') { // Update int l, r; long long val; cin >> l >> r >> val; range_update(l, r, val); } } return 0; } ``` --- #### 代码解析 1. **初始化部分** - 使用 `init()` 函数初始化树状数组,输入初始数组并将其转换为差分形式存储到树状数组中[^3]。 2. **更新操作 (`range_update`)** - 将区间 `[l, r]` 上的每个元素加上 `val` 转化为两个单点修改: - 在位置 `l` 处增加 `val`; - 在位置 `r+1` 处减去 `val`(如果存在的话)[^3]。 3. **查询操作 (`range_query`)** - 利用树状数组快速求解前缀和的功能,返回区间 `[l, r]` 的总和。 4. **时间复杂度分析** - 单次更新或查询的时间复杂度均为 \( O(\log n) \),因此适合大规模数据范围下的应用[^2]。 --- #### 差分数组的作用 差分数组的核心作用在于将复杂的区间修改简化为简单的单点修改。具体来说: - 如果需要对区间 `[l, r]` 执行加法操作,则只需调整差分数组中的两个端点即可[^3]。 这种技巧使得基于树状数组解决方案更加简洁高效。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值