【学习笔记】树状数组

这篇具有很好参考价值的文章主要介绍了【学习笔记】树状数组。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

树状数组是一种数据结构,普通树状数组维护的信息及运算要满足结合律且可差分。

一维树状数组

单点加、区间求和

树状数组是用长度为 \(n\) 的数组存储的。我们假设这个数组为 \(n\),令 lowbit(i)=i&(-i),则 \(c_i\) 保存的是向前 lowbit(i) 长度的 \(a\) 数组区间和。

【学习笔记】树状数组

单点加:从 \(i\) 开始,修改所有包含 \(a_i\)\(c_i\)\(c_i,c_{j=i+lowbit(i)} ,c_{j'^=j+lowbit(j)}\)……

区间求和 \([1,x]\):累加 \(c_x,c_{x'=x-lowbit(x)} ,c_{x''=x'-lowbit(x')}\)……整个过程就相当于不断把 \(x\) 的最后一个 \(1\) 消掉再不断累加。

区间求和 \([l,r]\):就是利用差分的思想——\([1,r]-[1,l-1]\)

时间复杂度均为 \(O(\log n)\)

PS:建树的不同方式

  • 直接一个一个修改,时间复杂度为 \(O(n\log n)\)

  • 可以先处理一个前缀和数组 \(s\),由于 \(c_i\) 表示的区间是 \([i-lowbit(i)+1,i]\),所以可以用 \(s_i-s_{i-lowbit(i)}\) 来表示 \(c_i\),时间复杂度为 \(O(n)\)

区间加、单点查询:

换一种思路,用树状数组 \(c\)\(c_i\) 存储向前 lowbit(i) 长度的 \(a\) 数组差分和,那么区间加、单点查询就转变成了原来的单点修改、区间查询。

例题

CF383C Propagating tree

有一颗 \(n\) 个节点的树,需要支持下列两种操作:

  • 把某个点的权值加 \(x\),其孩子权值加 \(-x\),其孩子的孩子权值加 \(-(-x)\)……
  • 查询某个节点的值

\(1\le n\le m\le 2\times 10^5\)

首先把树形转化为区间,可以想到 dfs 序,而看到区间修改、单点查询,考虑用树状数组维护差分数组。如果改变的是深度为奇数的点,就将差分数组加 \(v\),否则就减 \(v\),不难发现,树状数组维护的就相当于维护奇数层和偶数层的差。

而查询的时候也分深度奇偶性,如果是奇数就加上 \(1-x\) 的差分和,否则减去即可。

#include <bits/stdc++.h>
#define lowbit(i) i & (-i)
using namespace std;
const int N = 2e5 + 5;
int n, m, ts;
int a[N], l[N], r[N], c[N], d[N];
vector <int> p[N];
void dfs(int k, int fa) {
	d[k] = d[fa] + 1;
	l[k] = ++ts;
	for (auto i : p[k])
		if (i != fa) dfs(i, k);
	r[k] = ts;
}
void add(int x, int y) {
	for (int i = x; i <= n; i += lowbit(i))
		c[i] += y;
}
int query(int x) {
	int res = 0;
	for (int i = x; i>= 1; i -= lowbit(i))
		res += c[i];
	return res;	
 } 	
int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	for (int i = 1; i < n; i++) {
		int u, v;
		cin >> u >> v;
		p[u].push_back(v);
		p[v].push_back(u);
	}
	dfs(1, 0);
	while (m--) {
		int op, x, y;
		cin >> op >> x;
		if (op == 1) {
			cin >> y;
			if (d[x] & 1) add(l[x], y), add(r[x] + 1, -y);
			else add(l[x], -y), add(r[x] + 1, y);
		}
		else {
			int t = query(l[x]);
			if (d[x] & 1) cout << a[x] + t;
			else cout << a[x] - t;
			cout << "\n";
		}
	}
	return 0;
}

区间加、区间求和

设 a 的差分数组为 d,则:

\[\begin{aligned}a[1...r] & = \sum_{i=1}^r\sum_{j=1}^id_j \\& = \sum_{i=1}^rd_i\times(r-i+1)\\&=\sum_{i=1}^rd_i\times(r+1)-\sum_{i=1}^rd_i\times i\\&=(\sum_{i=1}^rd_i)\times(r+1)-\sum_{i=1}^rd_i\times i\end{aligned} \]

接下来可以用两个树状数组分别维护 \(d_i\)\(d_i\times i\)。若 \(a[l…r]\) 进行区间加 \(v\),那么维护 \(d_i\) 的树状数组就做 \(l\) 单点加 \(v\),对 \(r+1\) 单点加 \(-v\);对于维护 \(d_i\times i\) 的树状数组,则对 \(l\) 单点加 \(l\times v\),对 \(r+1\) 单点加 \(-v×(r+1)\) 即可。

int a[N], c1[N], c2[N];
void add(int x, int y) {
	for (int i = x; i <= n; i += lowbit(i))
		c1[i] += y, c2[i] += y * x;
}
int query(int x, int *c) {
	int res = 0;
	for (int i = x; i >= 1; i -= lowbit(i))
		res += c[i];
	return res;
}
void updata(int x, int y, int v) {
	add(x, v);
	add(y + 1, -v);
}
int ret(int x, int y) {
	return query(y, c1) * (y + 1) - query(y, c2) - query(x - 1, c1) * x + query(x - 1, c2);
}

二维树状数组

上面的所有内容都是关于一维树状数组的,其修改和查询的时间复杂度均为 \(O(\log n)\),空间复杂度均为 \(O(n)\)。接下的内容就是“二维树状数组”的,它的实现和一维有点类似。

单点加、子矩阵求和:

\(c_{i,j}\) 表示以 \(a_{i,j}\) 为右下角,高 lowbit(i),宽 lowbit(j) 的子矩阵的和。理解一下,这其实就是树状数组中套了一个树状数组,这一点具体在下文会体现。

单点加

要修改 \(a_{i,j}\),令 \(i'=i+lowbit(i),i''=i'+lowbit(i')...,j''=j+lowbit(j),j'=j'+lowbit(j')...\),则修改 \(c_{{i,i',i''...},{j,j',j''...}}\)。结合上文所说的树状数组套树状数组,就是对于所有需要修改的 \(i\)(和普通树状数组一样求法),再分别修改其对应的 \(j\),就是维护行的树状数组中修改列的树状数组。

查询子矩阵和

求以 \({i_1,j_1}\) 为左上角,\({i_2,j_2}\) 为右下角的子矩阵的和,利用二维差分的思想:\(s_{i_2,j_2 } -s_{i_2,j_1-1} -s_{i_1-1,j_2 } +s_{i_1-1,i_2-1}\)
\(s_{i,j}\) 表示的是以 \({1,1}\) 为左上角,\({i,j}\) 为右下角的子矩阵的和,令 \(i'=i-lowbit(i),i''=i'-lowbit(i')…j'=j-lowbit(j),j''=j'-lowbit(j')…\),则 \(s_{i,j} =c_{i,j} +c_{i,j'} +…+c_{i',j} +…\)。不难发现,这其实和普通树状数组的查询也有点像。

int c[N][N][C], a[N][N];
void add(int x, int y, int z, int v) {
	for (int i = x; i <= n; i += lowbit(i))
		for (int j = y; j <= m; j += lowbit(j))
			c[i][j][v] += z;
}
int query(int x, int y, int v) {
	int res = 0;
	for (int i = x; i >= 1; i -= lowbit(i))
		for (int j = y; j >= 1; j -= lowbit(j))
			res += c[i][j][v];
	return res;
}
int ret(int x, int y, int x1, int y1, int v) {
	return query(x1, y1, v) + query(x - 1, y - 1, v) - query(x - 1, y1, v) - query(x1, y - 1, v);
}

子矩阵加、子矩阵求和

思路和区间加、区间求和有些类似,都是运用了差分,只不过是扩展成了二维。

二维上的差分数组:\(d_{i,j}=a_{i,j}-a_{i,j-1}-a_{i-1,j}+a_{i-1,j-1}\),对于将左上角 \((i_1,j_1)\)、右下角 \((i_2,j_2)\) 的子矩阵加上 \(v\),就是将 \(d_{i_1,j_1 }\)\(d_{i_2+1,j_2+1}\)\(v\)\(d_{i_1,j_2+1}\)\(d_{i_2+1,j_1 }\)\(-v\)。接着推导:

\[\begin{aligned}a[1...i][1...j] & =\sum_{x=1}^i\sum_{y=1}^j\sum_{h=1}^x\sum_{k=1}^yd_{h,k}\\&=\sum_{x=1}^i\sum_{y=1}^jd_{x,y}\times(i-x+1)\times(j-y+1)\\&=\sum_{x=1}^i\sum_{y=1}^jd_{x,y}\times(ij-iy+i-xj-xy-x+j-y+1)\\&=(\sum_{x=1}^i\sum_{y=1}^jd_{x,y})\times(ij+i+j+1)-(\sum_{x=1}^i\sum_{y=1}^jd_{x,y}\times x)\times(j+1)-(\sum_{x=1}^i\sum_{y=1}^jd_{x,y}\times y)\times(i+1)+\sum_{x=1}^i\sum_{y=1}^jd_{x,y}\times xy\end{aligned} \]

维护 \(d_{x,y},d_{x,y}\times x,d_{x,y}\times y,d_{x,y}\times xy\) 的树状数组,其它的细节比如要修改哪些、怎么查询都和单点加、子矩阵查询的树状数组一样。

顺便说一句,如果是只需要子矩阵修改、单点查询的话,那么就和区间修改、单点查询一样,只需维护二维差分数组再求二维前缀和即可。

int c1[N][N], c2[N][N], c3[N][N], c4[N][N];
void add(int x, int y, int v) {
	for (int i = x; i <= n; i += lowbit(i))
		for (int j = y; j <= m; j += lowbit(j)) {
			c1[i][j] += v;
			c2[i][j] += x * v;
			c3[i][j] += y * v;
			c4[i][j] += x * y * v;
		}
}
int query(int x, int y) {
	int res = 0;
	for (int i = x; i >= 1; i -= lowbit(i))
		for (int j = y; j >= 1; j -= lowbit(j))
			res += c1[i][j] * (x + 1) * (y + 1) - c2[i][j] * (y + 1) - c3[i][j] * (x + 1) + c4[i][j];
	return res;
}
void updata(int i1, int j1, int i2, int j2, int v) {
	add(i1, j1, v), add(i2 + 1, j2 + 1, v);
	add(i1, j2 + 1, -v), add(i2 + 1, j1, -v);
}
int getsum(int i1, int j1, int i2, int j2) {
	return query(i2, j2) - query(i2, j1 - 1) - query(i1 - 1, j2) + query(i1 - 1, j1 - 1);
}

以上是二维树状数组的全部内容,依旧可以发现,无论是子矩阵修改还是单点修改,它们的修改、查询的时间复杂度均为 \(O(\log^2 n)\),空间复杂度为 \(O(n^2)\)

其它应用

权值树状数组

权值树状数组的实现和普通树状数组差不多,只不过其维护的信息是权值罢了,其经常用于解决和偏序相关的问题。

例题

洛谷 P1637 三元上升子序列

  • 求数列中满足 \(i<j<k\)\(a_i<a_j<a_k\) 的三元组 \((a_i,a_j,a_k )\) 的个数。
  • \(n\le3×10^4,a_i\le10^5\)

枚举中间的元素 \(a_j\),分别求其左边满足条件的 \(a_i\) 的个数 \(left\) 和其右边满足条件的 \(a_k\) 的个数 \(right\),则对答案的贡献为 \(left\times right\)
\(left\) 为例,在枚举 \(a_j\) 的同时用权值树状数组维护 \(a_1-a_{j-1}\) 各个权值的个数,\(left\) 则为其中小于 \(a_i\) 的数个数(用权值树状数组查询)。\(right\) 同理。

#include <bits/stdc++.h>
#define int long long
#define lowbit(x) x & (-x)
using namespace std;
const int N = 5e5 + 5;
int n;
int a[N], b[N], h[N];
int t[N];
void updata(int x, int y){
	for (int i = x; i <= 1e5; i += lowbit(i))
		t[i] += y;
}
int query(int x) {
	int res = 0;
	for (int i = x; i; i -= lowbit(i))
		res = res + t[i];
	return res;
 }
signed main() {
	int m;
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	int s1[N];
	for (int i = 1; i <= n; i++) {
		s1[i] = query(a[i] - 1);
		updata(a[i], 1);
	}
	int s2[N];
	memset(t, 0, sizeof(t));
	for (int i = n; i >= 1; i--) {
		s2[i] = n - i - query(a[i]);
		updata(a[i], 1);
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
		ans += s1[i] * s2[i];
	cout << ans << "\n";
	return 0;
}

树状数组套权值线段树

树状数组套权值线段树就是把权值线段树看作一个整体,把普通线段树中维护的值换作一颗颗线段树而已。

例题

洛谷 P2617 Dynamic Rankings

单点修改、查询区间第 \(k\)

\(1\le n,m\le 10^5\)

如果是静态查询区间第 \(k\) 小可以使用可持久化线段树(主席树),但如果是动态的,修改时它的复杂度就会变成 \(O(n\log n)\),可以用树状数组套权值线段树来优化:主席树的思想为维护 \([1,1][1,2]...[1,n]\) 的权值线段树,其管理的只是一个区间,而树状数组套权值线段树就不一样了,\([1,r]\) 管理的是 \([1,r-lowbit(r)+1]-[1,r]\) 的权值线段树。注意,这里的树状数组并没有一个具体的数组 \(c\),而只是借用了其的思想而已。

单点查询:在树状数组上求出要修改哪几颗线段树,直接递归修改,这些操作和普通线段树的修改操作相同。

查询区间第 \(k\) 小:由于所有线段树的结构是相同的,所以可以直接在多棵树上进行线段树上二分,其他和用线段树查询差不多,只不过是先用树状数组上记录所有需要的线段树二分用。

单次操作时间复杂度:\(O(\log^2 ⁡n)\)

空间复杂度(动态开点):\(O(n\log^2 ⁡n)\)

以下是核心代码(省略主函数的输入、离散化、处理询问):

const int N = 1e5 + 5;
struct node {
	int l, r;
	int v;
}t[N * 200];
int n, m, cnt, len, ts;
int a[N], b[N * 2], c[N], l1[N], l2[N], l3[N];
int rt[N];
char op[N];
void pushup(int k) {
	t[k].v = t[t[k].l].v + t[t[k].r].v;
}
int updata(int k, int l, int r, int x, int v) {
	if (!k) k = ++ts;
	if (l == r) {
		t[k].v += v;
		return k;
	}
	int m = l + ((r - l) >> 1);
	if (x <= m) t[k].l = updata(t[k].l, l, m, x, v);
	else t[k].r = updata(t[k].r, m + 1, r, x, v);
	pushup(k);
	return k;
}
int query(int t1[], int t2[], int s1, int s2, int l, int r, int k) {
	if (l == r) return l;
	int m = l + ((r - l) >> 1), ls = 0;
	for (int i = 1; i <= s1; i++)
		ls += t[t[t1[i]].l].v;
	for (int i = 1; i <= s2; i++)
		ls -= t[t[t2[i]].l].v;
	if (ls >= k) {
		for (int i = 1; i <= s1; i++) t1[i] = t[t1[i]].l;
		for (int i = 1; i <= s2; i++) t2[i] = t[t2[i]].l;
		return query(t1, t2, s1, s2, l, m, k);
	}
	for (int i = 1; i <= s1; i++) t1[i] = t[t1[i]].r;
	for (int i = 1; i <= s2; i++) t2[i] = t[t2[i]].r;
	return query(t1, t2, s1, s2, m + 1, r, k - ls);
}
void add(int x, int v) {
	int xx = lower_bound(b + 1, b + 1 + len, a[x]) - b;
	for (int i = x; i <= n; i += lowbit(i))
		rt[i] = updata(rt[i], 1, len, xx, v);
}
int init_query(int l, int r, int k) {
	int t1[N], t2[N], s1 = 0, s2 = 0;
	for (int i = r; i >= 1; i -= lowbit(i))
		t1[++s1] = rt[i];
	for (int i = l - 1; i >= 1; i -= lowbit(i))
		t2[++s2] = rt[i];
	return query(t1, t2, s1, s2, 1, len, k);
}

洛谷 P3810 【模板】三维偏序(陌上花开)

有 $ n $ 个元素,第 $ i $ 个元素有 $ a_i,b_i,c_i $ 三个属性,设 $ f(i) $ 表示满足 $ a_j \leq a_i $ 且 $ b_j \leq b_i $ 且 $ c_j \leq c_i $ 且 $ j \ne i $ 的 \(j\) 的数量。对于 $ d \in [0, n) $,求 $ f(i) = d $ 的数量。

\(1\le n\le 10^5\)

这道题可以用许多高级的算法完成,这边介绍一种用树状数组套权值线段树的做法。

从简到繁考虑,一维偏序直接排个序就可以了,二维偏序可以以 \(a_i\) 为关键字排个序,然后用权值树状数组/权值线段树查询即可。而三维偏序也是同样的思路,先以 \(a_i\) 为关键字排序,然后查询 \(b_j\le b_i\)\(c_j\le c_i\) 的个数(\(j\ne i\))。

不难想到用二维树状数组,行维护 \(b\),列维护 \(c\),修改是单点修改,查询则是查询左上角为 \((1,1)\),右下角为 \((b_i,c_i)\) 的子矩形和。但很可惜,这样的空间复杂度(离散化)是 \(O(n^2)\),不可实现。

那么可以把其中一个树状数组换换为线段树,在树状数组中维护 \(b\),在线段树种维护 \(c\),查询时先在树状数组中查找维护所有 \(\le b_i\) 的线段树,再在这些线段树中查找所有 \(\le c_i\) 的个数即可。

由于这道题是包括等于的,所以应先把所有相等的 \(a_i\) 加入“树状数组”再进行查询,注意这样得到的 \(f(i)\) 会多 \(1\)(包括自己)。

#include <bits/stdc++.h>
#define lowbit(i) i & (-i)
#define endl "\n"
using namespace std;
const int N = 1e5 + 5;
struct getcin {
	int x, y, z;
	int id;
}a[N];
struct node {
	int l, r;
	int v;
}t[N << 7];
int n, kkk, ts, cnt;
int tmp[N << 2], rt[N << 2], f[N], ans[N];
bool cmp(getcin x, getcin y) {
	if (x.x != y.x) return x.x < y.x;
	if (x.y != y.y) return x.y < y.y;
	return x.z < y.z;
}
void pushup(int k) {
	t[k].v = t[t[k].l].v + t[t[k].r].v;
}
int updata(int k, int l, int r, int x, int v) {
	if (!k) k = ++ts;
	if (l == r) {
		t[k].v += v;
		return k;
	}
	int m = l + ((r - l) >> 1);
	if (x <= m) t[k].l = updata(t[k].l, l, m, x, v);
	else t[k].r = updata(t[k].r, m + 1, r, x, v);
	pushup(k);
	return k;
}
int query(int l, int r, int x) {
	if (l == r) {
		int res = 0;
		for (int i = 1; i <= cnt; i++)
			res += t[tmp[i]].v;
		return res;
	}
	int m = l + ((r - l) >> 1);
	if (x <= m) {
		for (int i = 1; i <= cnt; i++)
			tmp[i] = t[tmp[i]].l;
		return query(l, m, x);
	}
	int sum = 0;
	for (int i = 1; i <= cnt; i++) {
		sum += t[t[tmp[i]].l].v;
		tmp[i] = t[tmp[i]].r;
	}
	return sum + query(m + 1, r, x);
}
void add(int x, int y, int v) {
	for (int i = x; i <= kkk; i += lowbit(i))
		rt[i] = updata(rt[i], 1, kkk, y, v);
}
int find(int x, int y) {
	cnt = 0;
	for (int i = x; i >= 1; i -= lowbit(i))
		tmp[++cnt] = rt[i];
	return query(1, kkk, y);
}
int main(){
	cin >> n >> kkk;
	for (int i = 1; i <= kkk; i++)
		rt[i] = ++ts;
	for (int i = 1; i <= n; i++)
		cin >> a[i].x >> a[i].y >> a[i].z, a[i].id = i;
	sort(a + 1, a + 1 + n, cmp);
	for (int i = 1; i <= n;) {
		int j = i;
		do {
			add(a[j].y, a[j].z, 1);
			j++;
		}while (a[j].x == a[j - 1].x);
		j = i;
		do {
			f[a[j].id] = find(a[j].y, a[j].z) - 1;
			j++;
		}while (a[j].x == a[j - 1].x);
		i = j;
	}
	for (int i = 1; i <= n; i++)
		ans[f[i]]++;
	for (int i = 0; i < n; i++)
		cout << ans[i] << endl;
	return 0;
}

练习题

  • CF1042D Petya and Array
  • P2184 贪婪大陆
  • P3586 LOG
  • CF961E Tufurama
  • CF896E The Untended Antiquity
  • P3380 【模板】二逼平衡树(树套树)

参考资料:OI Wiki文章来源地址https://www.toymoban.com/news/detail-610079.html

到了这里,关于【学习笔记】树状数组的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 树状数组的扩展应用

    「观前提醒」 「文章仅供学习和参考,如有问题请在评论区提出」 目录 O(N) 建树 方法一 方法二 维护区间和 单点修改,区间查询 区间修改,单点查询 区间修改,区间查询 维护二维子矩阵和(二维树状数组) 单点修改,子矩阵查询 子矩阵修改,单点查询 子矩阵修改,子矩

    2024年02月15日
    浏览(32)
  • 树状数组

    「观前提醒」 「文章仅供学习和参考,如有问题请在评论区提出」 目录 定义 基本概念 基本原理 单点修改 分析 代码实现 区间查询 分析 代码实现 整体代码 练手题目 小结 参考资料 树状数组 是一种支持 单点修改 和 区间查询 的数据结构。 普通的树状数组 只能用来维护像

    2024年02月16日
    浏览(35)
  • 超详细の树状数组讲解!

    以下有错误的话欢迎指正 由于篇幅问题每道题目的代码在每一板块最后折叠给出 其实线段树能维护的东西比树状数组能维护的东西多得多,但是树状数组代码好写啊! 最为常用的树状数组,我们一般都是用这个来解决问题,二维的后面会讲。 我们在进行数列操作的时候,经

    2024年02月07日
    浏览(34)
  • 深入理解树状数组

    树状数组(BIT, Binary Indexed Tree)是简洁优美的数据结构,它能在很少的代码量下支持 单点修改 和 区间查询 ,我们先以 a[] {1, 2, 3, 4, 5, 6} 数组为例建立树状数组看一下树状数组的样子: 可以发现:不是所有节点都是连接在一起的,c[1], c[2], c[3], c[4] 和 c[5], c[6] 分别构成了两棵

    2024年02月08日
    浏览(38)
  • 【算法】树状数组和线段树

    O ( l o g n ) O(logn) O ( l o g n ) :单点修改、区间查询 与前缀和的区别: 前缀和是离线的,每次动态修改原数组某个元素,都需要重新求一遍前缀和,因此单点修改是 O ( n ) O(n) O ( n ) 的,区间查询则是 O ( 1 ) O(1) O ( 1 ) 树状数组是在线的,单点修改和区间查询都是 O ( l o g n ) O(

    2024年02月19日
    浏览(47)
  • 数据结构<1>——树状数组

    前言:树状数组能解决的问题线段树一定可以解决。然后关于线段树的内容会在2中讲解。 树状数组,也叫Fenwick Tree和BIT(Binary Indexed Tree),是一种支持 单点修改 和 区间查询 的,代码量小的数据结构。 那神马是单点修改和区间查询?我们来看一道题。 洛谷P3374(模板): 在本题中

    2024年01月25日
    浏览(61)
  • 树状数组练习题

    为什么大佬们一眼看出是树状数组呢? 不是一眼看出树状数组,而是 要把 【找两个相邻的最小值之间还有几个数没删掉】 的问题转变成动态区间和问题,这个转化过程才是最重要的 , 至于你用什么数据结构求动态区间和是次要的,很多数据结构都能做,最常用的树状数组

    2024年02月03日
    浏览(39)
  • 243. 一个简单的整数问题2(树状数组)

    输入样例: 输出样例:  解析:         一般树状数组都是单点修改、区间查询或者单点查询、区间修改。这道题都是区间操作。                    1. 区间修改用数组数组维护差分数组         2. 区间查询,需要log计算两个端点的前缀和。上图右侧,可以得出,计算

    2024年02月14日
    浏览(38)
  • 《算法竞赛进阶指南》0x42 树状数组

    题意: 二维平面给定一些点,询问 v 形和 ∧ 形数目 解析: 对于 ∧ 形: ( i , y ) (i,y) ( i , y ) ,考虑左右两侧比该点低的点的个数。树状数组查询 y j y y_j y y j ​ y 的点的个数。因为总共有 y − 1 y-1 y − 1 个点比当前点低,有 n − y n-y n − y 个点比当前点高。 v型同理。 代码

    2023年04月11日
    浏览(40)
  • 把树状数组在页面显示成‘/‘/‘形式,并搜索想要的值

    大概思路 在Vue中,若要将树状数组以类似于文件路径的形式(即“/”分隔)显示在页面上,可以按照以下步骤操作: 首先,假设您有一个树状数组,其结构可能如下所示: 接下来,在Vue组件中,您可以编写一个计算属性或方法来递归地处理这个树形结构并将其转换为包含路

    2024年01月16日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包