树状数组(南昌理工学院)
1. 一维树状数组的应用场景
解决涉及一维区间的在线操作问题。(如单点修改 + 区间查询、区间修改 + 单点查询、区间修改 + 区间查询)
1.1单点修改 + 区间查询
inline int lowbit(int x) // 求二进制下最低位的1
{
return x & -x;
}
inline void add(int x, int d) //单点修改 给第x号元素 + d
{
while (x <= n) {
aa[x] += d;
x += lowbit(x);
}
}
inline int ask(int x) // 求从第一个元素 到 第x个元素的总和
{
int res = 0;
while (x) {
res += aa[x];
x -= lowbit(x);
}
return res;
}
inline int getsum(int x, int y) // 求区间[l, r]内的元素总和
{
return ask(r) - ask(l - 1);
}
1.2区间修改 + 单点查询
例题传送门
原理:利用差分的思想对区间[l, r]在O(1)的时间复杂度内进行修改
inline int lowbit(int x) // 求二进制下最低位的1
{
return x & -x;
}
void add(int x, int d) //修改点
{
while (x <= n) {
p[x] += d;
x += lowbit(x);
}
}
void range_add(int l, int r, int d) //经过差分处理后, p数组修改只要在第l个元素的 +d 和第r + 1个元素-d即可完成区间修改
{
add(l, d); add(r + 1, -d);
}
int ask(int x) //求第x个位置的元素值
{
int res = 0;
while (x) {
res += p[x];
x -= lowbit(x);
}
return res;
}
1.3区间修改 + 区间查询
原理:利用差分的思想对区间进行修改,再通过数学公式的推导得出如何对区间进行维护。
通过对sum1数组,sum2数组的维护对区间[l, r]进行查询。
例题传送门
typedef long long ll;
ll sum1[N], sum2[N];
inline int lowbit(int x) // 求二进制下最低位的1
{
return x & -x;
}
inline void add(ll x, ll d) // 对sum1数组和sum2数组进行维护
{
for (int i = x; i <= n; i += lowbit(i)) {
sum1[i] += d; sum2[i] += x * d;
}
}
inline void range_add(ll l, ll r, ll d) // 差分思想进行区间维护
{
add(l, d); add(r + 1, -d);
}
inline ll ask(ll x) // 求[1, x]内的所有元素之和
{
ll res = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
res += (x + 1) * sum1[i] - sum2[i];
}
return res;
}
inline ll range_ask(ll l, ll r) //求区间[l, r]内所有元素之和
{
return ask(r) - ask(l - 1);
}
1.4树状数组其他神奇操作
1.4.1求逆序对数量
用树状数组解逆序对用到一个技巧:把数字看成树状数组的下标。每处理一个数字,树状数组的下标所对应的元素数值加一,统计前缀和,就是逆序对的数量。倒序或正序处理数据都行。(处理前要离散化)
例题传送门
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 7;
int n;
int tr[N], a[N];
vector<int> mp;
ll ans;
inline int lowbit(int x)
{
return x & -x;
}
void add(int x, int d)
{
while (x <= n) {
tr[x] += d;
x += lowbit(x);
}
}
int ask(int x)
{
int res = 0;
while (x) {
res += tr[x];
x -= lowbit(x);
}
return res;
}
inline int get_id(int x)
{
return lower_bound(mp.begin(), mp.end(), x) - mp.begin() + 1;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), mp.push_back(a[i]);
//离散化
sort(mp.begin(), mp.end());
mp.erase(unique(mp.begin(), mp.end()), mp.end());
// 先序遍历找这个点前有多少个点比它大
for (int i = 1; i <= n; ++i) {
int u = get_id(a[i]);
add(u, 1);
ans = ans + i - ask(u);
}
/*
//后序遍历,看在这个点后有多少个点比它小
for (int i = n; i > 0; --i) {
int u = get_id(a[i]);
add(u, 1);
ans += ask(u - 1);
*/
printf("%lld\n", ans);
return 0;
}
2. 二维树状数组
2.1 单点修改 + 二维区间查询
其实和一维梳妆数组差不多,只是把一维变成二维,多了重y轴方向for循环。
inline int lowbit(int x)
{
return x & -x;
}
void add(int x, int y, int d)
{
for (int i = x; i <= n; i += lowbit(i))
for (int j = y; j <= n; j += lowbit(j))
a[i][j] += d;
}
int ask(int x, int y)
{
int res = 0;
for (int i = x; i > 0; i -= lowbit(i))
for (int j = y; j > 0; j -= lowbit(j))
res += a[i][j];
return res;
}
int range_ask(int x1, int y1, int x2, int y2)
{
return ask(x2, y2) - ask(x1 - 1, y2) - ask(x2, y1 - 1) + ask(x1 - 1, y1 - 1);
}
2.1 二维区间修改 + 单点查询
利用二维差分的思想,进行区间修改,然后用树状数组求和进行单点查询。
inline int lowbit(int x)
{
return x & -x;
}
void update(int x, int y, int d) //单点修改
{
for (int i = x; i <= n; i += lowbit(i))
for (int j = y; j <= m; j += lowbit(j));
tree[i][j] += d;
}
void ranger_update(int x1, int y1, int x2, int y2, int d) //二维区间修改
{
update(x1, y1, d);
update(x1, y2 + 1, -d);
update(x2 + 1, y1, -d);
update(x2 + 1, y2 + 1, d);
}
void ask(int x, int y)
{
int res = 0;
for (int i = x; i > 0; i -= lowbit(i))
for (int j = y; j > 0; j -= lowbit(j))
res += tree[i][j];
}
2.1 二维区间修改 + 二维区间查询
利用差分的思想,进行区间修改,然后用二维前缀和的思想+树状数组进行区间查询。
inline int lowbit(int x)
{
return x & -x;
}
void update(int x, int y, int d) //单点修改
{
for (int i = x; i <= n; i += lowbit(i))
for (int j = y; j <= m; j += lowbit(j));
tree[i][j] += d;
}
void ranger_update(int x1, int y1, int x2, int y2, int d) //二维区间修改
{
update(x1, y1, d);
update(x1, y2 + 1, -d);
update(x2 + 1, y1, -d);
update(x2 + 1, y2 + 1, d);
}
int ask(int x, int y) // 单点查询
{
int res = 0;
for (int i = x; i > 0; i -= lowbit(i))
for (int j = y; j > 0; j -= lowbit(j))
res += tree[i][j];
return res;
}
int ranger_ask(int x1, int y1, int x2, int y2) //利用二维差分进行二维区间查询
{
return ask(x2, y2) - ask(x2, y1 -1) - ask(x1 - 1, y2) + ask(x1 - 1, y1 - 1);
}