综述
树状数组其实就是一个长得像树的数组
下图为二叉树, 假如每个父亲存的是两个儿子的值(线段树), 便有了树状数组的样子
下图为树状数组
黑色表示原来的数组(a[i]), 红色是树状数组(c[i]), 每个位置存的是子节点的和
c1 = a[1];
c[2] = a[1] + a[2];
c[3] = a[3];
c[4] = a[1] + a[2] + a[3] + a[4];
c[5] = a[5];
c[6] = a[5] + a[6];
c[7] = a[7];
c[8] = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7];
c[i] = a[i - 2 ^ k + 1] + a[i - 2 ^ k + 2] … + a[i] // k为i的二进制中从最低位到高位连续零的长度, Eg. 8-> 1000 => k = 3
基本要素
lowbit()
检验数组内有多少个数
lowbit(x) = 2 ^ k; // 取出x的最低位1, k同上
x = 0 => 结果 = 0
x 奇数 => 结果 = 1
x 偶数 => 结果为x中2的最大次方的因子
int lowbit(int x)
{
return x & (-x);
}
modify()
修改值, 更新过程是查询过程的逆向
更新a[1]时, 需要向上更新C[1], C[2], C[4], C[8]
void modify(ll x, ll w)
{
while (x <= n)
{
a[x] += w;
x += lowbit(x);
}
}
query()
前缀和
ll query(ll x)
{
ll s = 0;
while(x > 0)
{
s += c[x];
x -= lowbit(x);
}
return s;
}
例题
单点修改, 区间查询
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[1111110];
ll n, q, i, j, t, w, x, tt;
ll lowbit(ll x)
{
return x & (-x);
}
void modify(ll x, ll w)
{
while (x <= n)
{
a[x] += w;
x += lowbit(x);
}
}
ll query(ll x)
{
ll s = 0;
while (x > 0)
{
s += a[x];
x -= lowbit(x);
}
return s;
}
int main()
{
cin >> n >> q;
for (i = 1; i <= n; i++)
{
cin >> w;
modify(i, w);
}
while (q--)
{
cin >> x >> t >> tt;
if (x == 1)
modify(t, tt);
if (x == 2)
cout << query(tt) - query(t - 1) << endl;
}
return 0;
}
区间修改,单点查询
要做到区间修改,我们能想到的省时间的做法是什么—差分前缀和
打个比方,也就是说我们要给区间[1,3]的每个数加上4,我们只需要给差分数组的a[1]加上4,给差分数组的a[4]减去4
#include <iostream>
#include <string>
using namespace std;
typedef long long ll;
ll a[1111110];
ll n, q, i, j,k, t, w, x, tt;
ll b, c, d, now, last, num = 1;
ll lowbit(ll x)
{
return x & (-x);
}
void modify(ll x, ll w)
{
while (x <= n)
{
a[x] += w;
x += lowbit(x);
}
}
ll query(ll x)
{
ll s = 0;
while (x > 0)
{
s += a[x];
x -= lowbit(x);
}
return s;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> q;
for (i = 1; i <= n; i++)
{
cin >> now;
modify(i, now - last);
last = now;
}
while (q--)
{
cin >> k;
if (k == 1)
{
cin >> b >> c >> d;
modify(b, d);
modify(c + 1, -d);
}
else if (k == 2)
{
cin >> b;
cout << query(b) << endl;
}
}
return 0;
}
逆序对
猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。
最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i > a j a_i>a_j ai>aj 且 i < j i<j i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。
#include <bits/stdc++.h>
#define M 500005
using namespace std;
int a[M], d[M], t[M], n, i;
int lowbit(int x)
{
return x & -x;
}
void modify(int x) // 把包含这个数的结点都更新
{
while (x <= n) // 范围
{
t[x]++;
x += lowbit(x);
}
}
int sum(int x) // 查询1~X有几个数加进去了
{
int res = 0;
while (x >= 1)
{
res += t[x];
x -= lowbit(x);
}
return res;
}
bool cmp(int x, int y) // 离散化比较函数
{
if (a[x] == a[y])
return x > y; // 避免元素相同
return a[x] > a[y]; // 按照原序列第几大排列
}
int main()
{
long long ans = 0;
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i], d[i] = i;
sort(d + 1, d + n + 1, cmp); // 离散化
for (i = 1; i <= n; i++)
{
modify(d[i]); // 把这个数放进去
ans += sum(d[i] - 1); // 累加
}
cout << ans;
return 0;
}
最大交点数
一个终端是一排 n n n 个连接在一起的相等的线段,有两个终端,一上一下。
有一个数组 a i a_i ai,代表从上面的终端中第 i i i 条线段,到下面的终端中第 a i a_i ai 条线段,有一条连线。
问这些连线最多有几个交点。
只有当某一条线段的上端点大于(没有等于的情况)当前的线段的上端点,而且下端点<= 当前的线段的下端点时两线才会交叉
问题转化为 a[j] ≤ a[i] (i < j) 的方案数, 即求逆序对的个数
每次都是修改一个点 然后求一段区间和
#include <bits/stdc++.h>
#define int long long
using namespace std;
int tt;
int n, i, ans, a[200010], b[200010], c[200010];
int lowbit(int x)
{
return x & (-x);
}
void modify(int x, int w)
{
while (x <= n)
{
c[x] += w;
x += lowbit(x);
}
}
int query(int x)
{
int s = 0;
while (x > 0)
{
s += c[x];
x -= lowbit(x);
}
return s;
}
void solve()
{
cin >> n;
memset(c, 0, sizeof(c));
ans = 0;
for (i = 1; i <= n; i++)
b[i] = 0;
for (i = 1; i <= n; i++)
{
cin >> a[i];
b[a[i]]++;
}
for (i = 1; i <= n; i++)
ans += b[i] * (b[i] - 1) / 2;
for (i = n; i >= 1; i--)
{
modify(a[i], 1);
if (a[i] > 1)
ans += query(a[i] - 1);
}
cout << ans << endl;
}
signed main()
{
cin >> tt;
while (tt--)
solve();
}
参考
- https://www.cnblogs.com/xenny/p/9739600.html