树状数组
\(\text{loj-130}\)
给定数列 \(a_1, a_2, \dots, a_n\),你需要依次进行 \(q\) 个操作,操作有两类:
1 i x:给定 \(i,x\),将 \(a_i\) 加上 \(x\);2 l r:给定 \(l,r\),求 \(\sum_{i=l}^r a_i\) 的值(换言之,求 \(a_l+a_{l+1}+\dots+a_r\) 的值)。
\(1 \le n,q \le 10^6\),\(|a_i|,|x| \le 10^6\),\(1 \le l \le r \le n\)。
树状数组板子。
树状数组是这样的,可以完成单点修改、区间查询或者区间修改、单点查询,不能混用。
需要一个计算 \(\text{lowbit}\) 的函数:
long long lowbit(long long x) { return x & (-x); }
接着是单点修改:
void add(long long x, long long k) {
while(x < MAXN) t[x] += k, x += lowbit(x);
return;
}
区间查询(实际上是前缀和查询,但可以 \(\text{query}(r) - \text{query}(l - 1)\) 做到区间查询):
long long query(long long x) {
long long res = 0;
while(x) res += t[x], x -= lowbit(x);
return res;
}
树状数组的优势就是简单易懂、码量少,但是缺点也很明显,功能太少了。
\(\text{loj-10114}\)
给定 \(n\) 个点,定义每个点的等级是在该点左下方(含正左、正下)的点的数目。
点按 \(y\) 坐标增序给出,\(y\) 坐标相同的按 \(x\) 坐标增序给出。
统计每个等级有多少个点。
\(1 \le n \le 1.5 \times 10^4\),\(0 \le x,y \le 3.2 \times 10^4\)。
对于 \(n\) 个点遍历,维护当前在 \(x\) 轴上 \([0, p]\) 区间的点数即可。
用树状数组维护,单点修改、区间查询。
注意:树状数组维护的左端点只能从 \(1\) 开始,所以需要将每个坐标 \(+1\) 处理。
\(\text{loj-10115}\)
校门外有很多树,学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两种操作:
1 l r表示在 \([l,r]\) 种上一种树,每次操作种的树的种类都不同;
2 l r表示询问 \([l,r]\) 区间内树的种类数。
注意:每个位置都可以重复种树。
\(1 \le n,m \le 5 \times 10^4\),\(1 \le l,r \le n\)。
显然是树状数组的题,刚开始想的直接维护区间修改、单点查询,但发现好像没这么简单。
例如 \([1,3]\) 种上树,询问 \([2,5]\) 时,\([1,5]\) 共有一种树,\([1,1]\) 也有一种树,相减显然不是答案。
那么怎么处理呢?让我想想。
考虑把左右端点分开维护,开两个树状数组,分别维护 \([1,x]\) 左端点数目 \(t_{x,0}\) 和右端点数目 \(t_{x,1}\)。
比如 \([2,6]\)、\([5,10]\) 种上树,询问 \([8,12]\) 时,\([1,12]\) 左端点数目有 \(2\) 个,而 \([1,7]\) 右端点数目有 \(1\) 个,说明有一种树完全在给定区间的左边,减去即可。
因此答案为 \(t_{r,0} - t_{l-1,1}\),这样就处理完了。
\(\text{loj-10117}\)
有一个长度为 \(n\) 的 \(01\) 数组 \(a_i\),初始值均为 \(0\)。给定 \(m\) 条指令:
1 l r表示将 \([l,r]\) 区间内的数反转。2 x表示询问 \(a_x\) 的值。
\(1 \le n \le 10^5\),\(1 \le m \le 5 \times 10^5\),\(1 \le l \le r \le n\)。
简单的树状数组维护区间修改、单点查询,\(a_i\) 被反转奇数次为 \(1\),否则为 \(0\)。
\(\text{loj-133}\)
给出一个 \(n\times m\) 的零矩阵 \(A\),你需要完成如下操作:
1 x y k:表示元素 \(a_{x,y}\) 自增 \(k\);2 a b c d:表示询问左上角为 \((a,b)\),右下角为 \((c,d)\) 的子矩阵内所有数的和。
\(1 \le n,m \le 2^{12}\),\(1 \le x,a,c \le n\),\(1 \le y,b,d \le m\),\(|k| \le 10^5\)。
保证操作次数不超过 \(3 \times 10^5\),且询问的子矩阵存在。
二维树状数组板子。
其实跟一维的差不太多,直接贴代码了:
long long lowbit(long long x) { return x & (-x); }
// (x,y) ~ (n,m) +1
void add(long long x, long long y, long long k) {
while(x <= n) {
long long py = y;
while(py <= m) t[x][py] += k, py += lowbit(py);
x += lowbit(x);
}
return;
}
// 求 (1,1) ~ (x,y) 的和
long long query(long long x, long long y) {
long long res = 0;
while(x) {
long long py = y;
while(py) res += t[x][py], py -= lowbit(py);
x -= lowbit(x);
}
return res;
}
\(\text{luogu-1972}\)
给定一个长度为 \(n\) 的序列 \(a\),有 \(m\) 次询问,每次询问给定 \([l,r]\),回答区间内不同数的个数。
\(1 \le n, m, a_i \le 10^6\),\(1 \le l \le r \le n\)。
把询问离线下来,挂到右端点上。值域树状数组维护 \(x\) 最后一次出现的下标。
那么扫到 \(r_i\) 时,询问 \([l_i, r_i]\) 的答案就是 \(\text{qry}(r) - \text{qry}(l-r)\)。
然后就做完了。
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define ll long long
#define MAXN 1000005
#define pii pair<ll, ll>
#define fi first
#define se second
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
ll n, m, a[MAXN], lst[MAXN], ans[MAXN], t[MAXN];
vector<pii > v[MAXN];
ll lowbit(ll x) { return x & (-x); }
void add(ll x, ll k) { while(x < MAXN) t[x] += k, x += lowbit(x); return; }
ll qry(ll x) { ll res = 0; while(x) res += t[x], x -= lowbit(x); return res; }
int main() {
n = read();
for(int i = 1; i <= n; i ++) a[i] = read();
m = read();
for(int i = 1; i <= m; i ++) {
ll l = read() - 1, r = read();
v[r].push_back({l, i});
}
for(int i = 1; i <= n; i ++) {
if(!lst[a[i]]) add(i, 1);
else add(lst[a[i]], -1), add(i, 1);
lst[a[i]] = i;
for(auto it : v[i]) ans[it.se] = qry(i) - qry(it.fi);
}
for(int i = 1; i <= m; i ++) cout << ans[i] << "\n";
return 0;
}
本文来自博客园,作者:So_noSlack,转载请注明原文链接:https://www.cnblogs.com/So-noSlack/p/19229033

浙公网安备 33010602011771号