离散化与树状数组:下标映射、更新与查询

发布于 2026-02-05 16:22 734 字 4 min read

hainoir avatar

hainoir

记录前端实践、算法推演和那些值得反复理解的问题。

  1. 至今

    超星

    前端研发工程师

  2. 至

    好未来

    硬件运维工程师

首页 周刊 时间线 关于 歌单
从排序去重和下标映射理解离散化,再梳理树状数组的 lowbit、更新与前缀查询;附单点修改、区间求和,以及用差分维护区间更新的 C++ 模板。

离散化

​ 离线操作(存下操作,后执行)

//找出所有操作点,存下
//将相关点排序去重
//将大点转化为小点
//操作 全部转化到小点上 

https://www.starrycoding.com/problem/63

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 3e5+9;

vector<int> X;//离散化得到小点(相关的点)
ll a[N];

struct Q//存下操作和询问
{
	ll a,b;
}add[N],que[N];

int getidx(ll x)
{
	//lower_bound 找出数组中第一个 >= x的元素的迭代器
	//返回值的范围是[1,X.size()]
	return lower_bound(X.begin(),X.end(),x) - X.begin() + 1;
}

int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int n, q;cin >> n >> q;
	
	//n次操作发,在x点 加w
	for(int i = 1;i <= n;++i)
	{
		ll x,w;cin >> x >> w;
		X.push_back(x);//存下相关点
		add[i] = {x,w};//和上面分开存相关点,并且存下w,为之后离线操作准备
	}
	
	//q次询问,[l,r]内所有点的和
	//这里先存下询问区间,离线操作
	for(int i = 1;i <= q;++i)
	{
		ll l,r;cin >> l >> r;
		X.push_back(l);
		X.push_back(r);
		que[i] = {l,r};
	}
	
	sort(X.begin(),X.end());
	X.erase(unique(X.begin(),X.end()) ,X.end());//得到仅有相关点
	
	//在一条仅有相关点的数轴,给其中的相关点加上对应的值
	for(int i = 1;i <= n;++i)
	{
		int x = getidx(add[i].a);
		ll w = add[i].b;
		a[x] += w;
	}
	
	//相关点数值的前缀和
	for(int i = 1;i <= X.size();++i) a[i] += a[i-1];
	
	//离线操作,处理询问区间
	for(int i = 1;i <= q;++i)
	{
		int l = getidx(que[i].a);
		int r = getidx(que[i].b);
		cout << a[r] - a[l - 1] << '\n';
	}

}

树状数组(下标从 1 开始)

维护区间和

  • 单点修改O(1)
  • 区间修改O(nlogn)
  • 区间查询O(nlogn)

Ti的管辖区间:[i - lowbit(i) + 1 ,i],长度:lowbit(i)

单点修改与区间查询

https://www.starrycoding.com/problem/40

//树状数组单点修改模板(维护原数组的前缀和)

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5+9;
ll a[N],t[N];ll n,q;

int lowbit(int x){return x&-x;} //留下二进制的倒数第一个1

void update(int k ,ll x){
	for(int i = k;i <= n;i += lowbit(i)) t[i] += x; //将树状数组t赋值
}

ll getsum(int k)
{
	ll res = 0;
	for(int i = k;i > 0;i -= lowbit(i)) res += t[i];//得到k的前缀和
	return res;
}

int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin >> n >> q;
	for(int i = 1;i <= n;++i) cin >> a[i];
	
	for(int i = 1;i <= n;++i) update(i, a[i]);
	
	while(q--)
	{
		int op;cin >> op;
		if(op == 1)
		{
			ll k,v;cin >> k >> v;
			update(k,v);
		}else
		{
			ll l,r;cin >> l >> r;
			cout << getsum(r) - getsum(l-1) << '\n';
		}
	}
}

区间修改与区间查询

//树状数组维护差分
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 3e5+9;
ll a[N],td[N],tid[N];ll n,q;

int lowbit(int x){return x&-x;}

void update(int k ,ll x){
	for(int i = k;i <= n;i += lowbit(i)) td[i] += x,tid[i] += k * x;
}

ll getsum(int k)
{
	ll res = 0;
	for(int i = k;i > 0;i -= lowbit(i)) res += (k+1) * td[i] - tid[i];
	return res;
}

int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin >> n >> q;
	for(int i = 1;i <= n;++i) cin >> a[i];
	
	for(int i = 1;i <= n;++i) update(i, a[i]),update(i + 1, -a[i]);
	
	while(q--)
	{
		int op;cin >> op;
		if(op == 1)
		{
			ll l,r,v;cin >> l >> r >> v;
			update(l,v),update(r + 1,-v);
		}else
		{
			ll l,r;cin >> l >> r;
			cout << getsum(r) - getsum(l-1) << '\n';
		}
	}
}

本文作者:hainoir · CC BY-NC-SA 4.0

喜欢的话,留下你的评论吧~

评论加载中…

相关文章