基本概念
-
树状数组:把一个数组用树形结构维护,达到快速修改单点或区间的值、查询某个区间的和或单点的值的效果。这样维护的数组称为树状数组。树状数组查询和修改的时间复杂度均为\(O(log n)\)。
-
\(lowbit\)函数:假设有一个二进制数\(x\),其\(lowbit\)函数值为其从右往左数第一个\(1\)与其后面的\(0\)组成的二进制数。例如十进制数\(10\),转换成二进制为\(1010\),其\(lowbit\)函数值为二进制数\(10\),即为十进制数\(2\)。
一维树状数组
假设有一个数组\(a\),期望在\(O(logn)\)的时间复杂度内求出某一段区间的和或修改单个元素,此时就需要用到树状数组来维护。树状数组大致有三种类型:
-
单点修改,区间求和;
-
区间修改,单点求值;
-
区间修改,区间求和。
这三种典型问题均可以用树状数组来维护,此外还有区间最大值等变种问题。树状数组能解决的问题,线段树均可以解决;线段树可以解决的问题,树状数组不一定可以解决。树状数组的优点在于常数小、代码简单,在进行简单操作的时候比线段树更优。
树状数组的主要思想是:用一些大结点作为小结点的代表。三种树状数组均是采用了这种思想,只是有些树状数组还需要用到差分与前缀和的思想实现。
单点修改,区间求和
这种类型的树状数组支持修改数组中某个元素的值,并快速求出数组中一段连续区间的和。
假设要修改结点\(3\)的值,将结点\(3\)的值加上\(x\)。那么在树状数组中,结点\(3\)和它的代表需要被修改。问题是:谁是结点\(3\)的祖先?此处就要用到\(lowbit\)函数。可以发现一个规律,一个结点\(x\)的代表的编号,即为\(x\)本身的编号加上\(lowbit(x)\)。因此,结点\(3\)的祖先即为\(3 + lowbit(3) = 0011 + 0001 = 0110 = 4\)。
新的问题是:\(lowbit\)函数如何求解?答案是lowbit(x) = x & -x
。因为在计算机中,负数是以补码的方式存储的。补码实际上是原码(直接转换成二进制)按位取反后再加\(1\)。因此,原码是\(0\)的位数上,补码加一前一定是\(1\)。加上\(1\)后,补码显然会不断进位,直到遇到第一个\(0\),无法进位。因为补码的第一个\(0\)在原码中是第一个\(1\),此时因为进位,补码中的第一个\(0\)变成了\(1\),两者相与也显然是\(1\),其他位与的结果显然是\(0\)。因此,x & -x
实际上就是二进制数\(x\)从右往左数第一个\(1\)与其后面的数组成的二进制数。
根据上述内容,修改操作的流程是:先修改结点\(x\)的值,再从被修改的结点\(x\)开始,不断修改其代表的值,直到修改到根结点为止。\(x\)的代表即为\(x + lowbit(x)\)。
查询操作也是同理。从查询的结点\(x\)开始,不断累加树状数组中\(x - lowbit(x)\)的对应值,统计出被其代表的结点之和,最后再加上其本身,就是区间\(\left[ 1, x \right]\)的和。
参考代码如下:
#include <cstdio>
using namespace std;
#define maxn 500005
int n, m;
int a[maxn], c[maxn];
int lowbit(int x)
{
return x & (-x);
}
void add(int p, int x)
{
while (p <= n)
{
c[p] += x;
p += lowbit(p);
}
}
int getsum(int p)
{
int sum = 0;
while (p)
{
sum += c[p];
p -= lowbit(p);
}
return sum;
}
int main()
{
int op, x, y;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
add(i, a[i]);
}
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d", &op, &x, &y);
if (op == 1)
add(x, y);
else
{
int sumr = getsum(y);
int suml = getsum(x - 1);
printf("%d\n", sumr - suml);
}
}
return 0;
}
区间修改,单点求值
这种类型的树状数组支持修改数组中一段连续的区间,并快速求出数组中某个元素的值。
下面引入差分和前缀和的概念。假设有一数组\(a\),设\(s_{i}\)为数组中从元素\(1\)到元素\(i\)的和,\(d_{i}\)表示第\(i\)个元素与第\(i - 1\)个元素的差。显然,\(s_{i} = s_{i - 1} + a_{i}\),\(d_{i} = a_{i} - a_{i - 1}\)。对数组\(d\)求一遍前缀和,其前缀和数组的第\(i\)个元素即为数组\(a\)中的第\(i\)个元素。
差分对于修改区间的操作有着天然的亲和力。我们可以用一个树状数组来维护差分数组,每次在区间\(\left[l, r\right]\)加上同一个值\(x\)的时候,因为元素\(l\)在原来的基础上,又比元素\(l - 1\)多出了\(x\),所以\(d_{l} = d_{l} + x\);元素\(r + 1\)在原来的基础上,与元素\(r\)的差距减少了\(x\),所以\(d_{r + 1} = d_{r + 1} - x\)。
因此,修改一段区间\(\left[l, r\right]\)只需要令d[l] += x
,d[r + 1] -= x
即可。这样,第二类树状数组就转化成了第一类树状数组的问题。而根据差分的定义,只需要在树状数组上求一遍区间\(\left[1, x\right]\)的和即可求出数组\(a\)中的第\(x\)个元素的取值。
参考代码如下:
#include <cstdio>
using namespace std;
#define maxn 500005
int n, m;
int a[maxn], d[maxn], c[maxn];
int lowbit(int x)
{
return x & (-x);
}
void add(int p, int x)
{
while (p <= n)
{
c[p] += x;
p += lowbit(p);
}
}
int get(int x)
{
int sum = 0;
while (x)
{
sum += c[x];
x -= lowbit(x);
}
return sum;
}
void update(int l, int r, int x)
{
add(l, x);
add(r + 1, -x);
}
int main()
{
int op, x, y, k;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
d[i] = a[i] - a[i - 1];
add(i, d[i]);
}
for (int i = 1; i <= m; i++)
{
scanf("%d", &op);
if (op == 1)
{
scanf("%d%d%d", &x, &y, &k);
update(x, y, k);
}
else
{
scanf("%d", &x);
printf("%d\n", get(x));
}
}
return 0;
}
区间修改,区间求和
这种类型的树状数组支持快速修改某一段连续区间的值,并快速求出某一段连续区间的和。
同样地,这种类型的差分数组也需要用到差分和前缀和来维护:
显然,\(\sum\limits_{i = 1}^n a_{i} = \sum\limits_{i = 1}^n\sum\limits_{j = 1}^i d_{j}\)
在累加的过程中,\(d_{1}\)被累加了\(n\)次,\(d_{2}\)被累加了\(n - 1\)次,\(d_{3}\)被累加了\(n - 2\)次……\(d_{i}\)被累加了\(n - i + 1\)次。由此可以得到:
\(\sum\limits_{i = 1}^n a_{i} = \sum\limits_{i = 1}^n d_{i} \times (n - i + 1)\)
乘法分配律后得到\(\sum\limits_{i = 1}^n a_{i} = \sum\limits_{i = 1}^n d_{i} \times (n + 1) - \sum\limits_{i = 1}^n d_{i} \times i\)
令\(sum1\)数组为\(d_{i}\)数组,\(sum2\)数组为\(d_{i} \times i\)数组。区间\(\left[1, r\right]\)的和即为\(\sum\limits_{i = 1}^r sum1_{i} \times (i + 1) - sum2_{i}\),区间\(\left[1, l - 1\right]\)的和即为\(\sum\limits_{i = 1}^{l - 1} sum1_{i} \times (i + 1) - sum2_{i}\),两者相减即为区间\(\left[l, r\right]\)的和。
对于树状数组\(1\)的维护,直接用第二种树状数组的维护方法即可;对于树状数组\(2\)的维护类似,可以令sum2[l] += l * x
,sum2[r + 1] -= (r + 1) * x
。
参考代码如下:
#include <cstdio>
using namespace std;
const int maxn = 1e6 + 5;
int n, q;
long long a[maxn], d[maxn], t1[maxn], t2[maxn];
int lowbit(int x)
{
return x & (-x);
}
void add(int p, long long x)
{
long long temp = p * x;
while (p <= n)
{
t1[p] += x;
t2[p] += temp;
p += lowbit(p);
}
}
void update(int l, int r, long long x)
{
add(l, x);
add(r + 1, -x);
}
long long getsum(long long t[], int p)
{
long long sum = 0;
while (p)
{
sum += t[p];
p -= lowbit(p);
}
return sum;
}
long long get(int l, int r)
{
return ((r + 1) * getsum(t1, r) - l * getsum(t1, l - 1)) - (getsum(t2, r) - getsum(t2, l - 1));
}
int main()
{
int op, l, r;
long long x;
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
d[i] = a[i] - a[i - 1];
add(i, d[i]);
}
for (int i = 1; i <= q; i++)
{
scanf("%d", &op);
if (op == 1)
{
scanf("%d%d%lld", &l, &r, &x);
update(l, r, x);
}
else
{
scanf("%d%d", &l, &r);
printf("%lld\n", get(l, r));
}
}
return 0;
}
逆序对
树状数组也可以用于求某个序列的逆序对个数,时间复杂度为\(O(nlogn)\)。我们可以先把原数组\(a\)按从小到大排序一遍,得出每一个元素在排序后应该在的位置\(idx\)。我们遍历一遍原来的数组,并把\(cnt_{idx}\)加一,表示元素\(i\)又出现了一次。
此时用树状数组求一遍\(cnt\)数组\(1\)到\(idx\)的和,表示小于等于元素\(i\)的元素个数。用元素总数减去小于等于元素\(i\)的元素个数即为大于元素\(i\)的元素个数,也就是\(i\)贡献的逆序对个数。最后将每个元素的贡献累加起来即可。
如果数组\(a\)中含有重复元素,此时还需要用到离散化。具体见下文例题选讲部分。
区间最大值
树状数组不仅可以维护区间和,还可以维护区间最值。我们在进行更新的时候,将累加改为取最值就可以求出区间最值。代码略。
二维树状数组
既然有维护区间的一维树状数组,那么就肯定有维护矩阵的二维树状数组。在一维的树状数组中,\(tree_{x}\)表示以\(x\)为右端点的、长度为\(lowbit(x)\)的区间之和;在二维的树状数组中,\(tree_{x, y}\)表示右下角为\((x, y)\)且高为\(lowbit(x)\)、宽为\(lowbit(y)\)的矩阵之和。
单点修改,矩阵求和
这种类型的树状数组支持修改二维矩阵中的某个点\((x, y)\),并快速查询某个矩阵之和。
在这里引入二维差分和二维前缀和的概念。二维前缀和表示累加左上角为\((1, 1)\),右下角为\(x, y\)的子矩阵;二维差分表示在一维差分的基础上增加一个维度的差分。
设二维前缀和为\(sum_{x, y}\),则\(sum_{x, y} = sum_{x, y - 1} + sum_{x - 1, y} - sum_{x - 1, y - 1} + a_{x, y}\)。二维差分则可以在一维差分和二维前缀和的基础上类推得到递推公式,\(d_{x, y} = a_{x, y} - a_{x, y - 1} - a_{x - 1, y} + a_{x - 1, y - 1}\)。
假设我们需要求出左上角为\((a, b)\),右下角为\((c, d)\)的矩阵的元素之和,如下图:
设需要求的矩阵之和为\(S\)。显然\(S\)等于矩阵\((1, 1, i, j)\)减去其左边的矩阵\((1, 1, c, b - 1)\)再减去其上面的矩阵\((1, 1, a - 1, d)\)再加上被重复减去的矩阵\((1, 1, a - 1, b - 1)\)。因此,我们只需要用二维树状数组维护以\(1, 1\)为左上角,以每个点对\(i, j\)为右下角的矩阵之和即可。二维树状数组的维护可以类比一维树状数组,此处不多赘述。
参考代码如下:
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = (1 << 12) + 5;
int n, m;
long long tree[maxn][maxn];
int lowbit(int x)
{
return x & (-x);
}
void add(int x, int y, long long z)
{
int yy = y;
while (x <= n)
{
y = yy;
while (y <= m)
{
tree[x][y] += z;
y += lowbit(y);
}
x += lowbit(x);
}
}
long long getsum(int x, int y)
{
int yy = y;
long long sum = 0;
while (x)
{
y = yy;
while (y)
{
sum += tree[x][y];
y -= lowbit(y);
}
x -= lowbit(x);
}
return sum;
}
long long get(int xa, int ya, int xb, int yb)
{
return getsum(xb, yb) - getsum(xb, ya - 1) - getsum(xa - 1, yb) + getsum(xa - 1, ya - 1);
}
int main()
{
int op, a, b, c, d;
long long k;
scanf("%d%d", &n, &m);
while (scanf("%d", &op) != EOF)
{
if (op == 1)
{
scanf("%d%d%lld", &a, &b, &k);
add(a, b, k);
}
else
{
scanf("%d%d%d%d", &a, &b, &c, &d);
printf("%lld\n", get(a, b, c, d));
}
}
return 0;
}
矩阵修改,单点求值
假如要给一个左上角为\((xa, ya)\),右下角为\((xb, yb)\)的矩阵加上一个值\(x\),相当于在差分数组\((xa, ya)\)和\((xb + 1, yb + 1)\)的位置加上\(x\),在\((xa, yb + 1)\)和\((xb + 1, ya)\)的位置减去\(x\)。
例如要给下面这个矩阵中心的\(3 \times 3\)矩阵加上\(x\):
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
达到这样的效果:
0 0 0 0 0
0 x x x 0
0 x x x 0
0 x x x 0
0 0 0 0 0
相当于这样修改差分数组:
0 0 0 0 0
0 x 0 0 -x
0 0 0 0 0
0 -x 0 0 x
0 0 0 0 0
我们只需要用一个二维树状数组维护一个二维差分数组,在树状数组上求和即是待查询的点的取值。
参考代码如下:
#include <cstdio>
using namespace std;
const int maxn = (1 << 12) + 5;
int n, m;
long long tree[maxn][maxn];
int lowbit(int x)
{
return x & (-x);
}
void add(int x, int y, long long z)
{
int yy = y;
while (x <= n)
{
y = yy;
while (y <= m)
{
tree[x][y] += z;
y += lowbit(y);
}
x += lowbit(x);
}
}
void update(int xa, int ya, int xb, int yb, long long x)
{
add(xa, ya, x);
add(xa, yb + 1, -x);
add(xb + 1, ya, -x);
add(xb + 1, yb + 1, x);
}
long long get(int x, int y)
{
int yy = y;
long long sum = 0;
while (x)
{
y = yy;
while (y)
{
sum += tree[x][y];
y -= lowbit(y);
}
x -= lowbit(x);
}
return sum;
}
int main()
{
int op, a, b, c, d;
long long k;
scanf("%d%d", &n, &m);
while (scanf("%d", &op) != EOF)
{
if (op == 1)
{
scanf("%d%d%d%d%lld", &a, &b, &c, &d, &k);
update(a, b, c, d, k);
}
else
{
scanf("%d%d", &a, &b);
printf("%lld\n", get(a, b));
}
}
return 0;
}
矩阵修改,矩阵求和
这种类型的差分数组支持快速修改某个矩阵,快速求出某个矩阵之和。
用一个差分形式的式子来表示\(sum_{x, y}\):\(\sum\limits_{i = 1}^x \sum\limits_{j = 1}^y \sum\limits_{k = 1}^i \sum\limits_{l = 1}^j d_{k, l}\)
类比一维的第三类树状数组,统计出每一个差分数组中的点被统计的次数:\(d_{1, 1}\)被统计了\(x \times y\)次,\(d_{1, 2}\)被统计了\(x \times (y - 1)\)次……\(d_{i, j}\)被统计了\((n - i + 1) \times (m - j + 1)\)次。于是上面的式子就可以改写成:\(sum_{x, y} = \sum\limits_{i = 1}^x \sum\limits_{j = 1}^y d_{i, j} \times (x - i + 1) \times (y - j + 1)\)。
将该式子展开:
\(sum_{x,y} = \sum\limits_{i = 1}^x \sum\limits_{j = 1}^y (x + 1) \times (y - j + 1) \times d_{i, j} - i \times (y - j + 1) \times d_{i, j}\)
最终得到:
\(sum_{x, y} = \sum\limits_{i = 1}^x \sum\limits_{j = 1}^y (x + 1) \times (y + 1) \times d_{i, j} - (x + 1) \times j \times d_{i, j} - (y + 1) \times i \times d_{i, j} + i \times j \times d_{i, j}\)
如果前面的知识读者已经彻底掌握,相信此时读者也不难猜出我们要维护四个树状差分数组,分别是\(d_{i, j}\),\(d_{i, j} \times i\),\(d_{i, j} \times j\)和\(d_{i, j} \times i \times j\)。
参考代码如下:
#include <cstdio>
using namespace std;
const int maxn = 2500;
long long n, m;
long long t1[maxn][maxn], t2[maxn][maxn], t3[maxn][maxn], t4[maxn][maxn];
inline long long read()
{
long long res = 0, flag = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
flag = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
res = res * 10 + ch - '0';
ch = getchar();
}
return res * flag;
}
inline long long lowbit(long long x)
{
return x & (-x);
}
inline void add(long long x, long long y, long long z)
{
for (register long long i = x; i <= n; i += lowbit(i))
{
for (register long long j = y; j <= m; j += lowbit(j))
{
t1[i][j] += z;
t2[i][j] += x * z;
t3[i][j] += y * z;
t4[i][j] += x * y * z;
}
}
}
inline void update(long long xa, long long ya, long long xb, long long yb, long long x)
{
add(xa, ya, x);
add(xa, yb + 1, -x);
add(xb + 1, ya, -x);
add(xb + 1, yb + 1, x);
}
inline long long getsum(long long x, long long y)
{
long long sum = 0;
for (register long long i = x; i; i -= lowbit(i))
{
for (register long long j = y; j; j -= lowbit(j))
{
sum += (x + 1) * (y + 1) * t1[i][j];
sum -= (y + 1) * t2[i][j];
sum -= (x + 1) * t3[i][j];
sum += t4[i][j];
}
}
return sum;
}
inline long long get(long long xa, long long ya, long long xb, long long yb)
{
return getsum(xb, yb) - getsum(xb, ya - 1) - getsum(xa - 1, yb) + getsum(xa - 1, ya - 1);
}
int main()
{
long long op, a, b, c, d, x;
n = read();
m = read();
while (scanf("%lld", &op) != EOF)
{
if (op == 1)
{
a = read();
b = read();
c = read();
d = read();
x = read();
update(a, b, c, d, x);
}
else
{
a = read();
b = read();
c = read();
d = read();
printf("%lld\n", get(a, b, c, d));
}
}
return 0;
}
例题选讲
求逆序对
给定一个数组\(a\),定义一个逆序对为满足\(i < j\)且\(a_{i} > a_{j}\)的点对\((i, j)\),试求\(a\)中所有逆序对的个数。
我们可以用离散化的方法配合树状数组来解决这个问题,这个问题同样可以用值域线段树来解决。先对数组\(a\)进行排序、去重操作,得到\(a\)中每一个元素按从小到大排序后的排名。我们用排名作为关键字,枚举原序数组中的每一个元素,在树状数组中其排名的位置加一。
设某元素的排名为\(x\),排名为\(i\)的数目前出现的次数为\(cnt_{i}\),则其贡献的逆序对个数为\(\sum\limits_{i = x + 1}^{n}cnt_{i}\)。统计每一个元素贡献的逆序对个数,累加即可。
在此附上本人的\(AC\)代码,仅供参考,请勿抄袭:
#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 500005
int n, len;
int a[maxn], b[maxn], c[maxn];
int lowbit(int x) {
return x & (-x);
}
void update(int x) {
for (int i = x; i <= len; i += lowbit(i)) {
c[i]++;
}
}
int query(int x) {
int sum = 0;
for (int i = x; i; i -= lowbit(i)) {
sum += c[i];
}
return sum;
}
int main() {
long long ans = 0;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &b[i]);
a[i] = b[i];
}
sort(b + 1, b + n + 1);
len = unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; i++) {
int Rank = lower_bound(b + 1, b + len + 1, a[i]) - b;
update(Rank);
ans += (query(len) - query(Rank));
}
printf("%lld\n", ans);
return 0;
}
区间取反问题
给定若干个操作,每次可以将一段区间\(\left[l, r\right]\)取反,或询问某个元素现在的取值。试写出一个维护区间取反问题的数据结构。
显然,假如一个数被取反了奇数次,则该元素的取值等于其初始值取反;如果一个数被取反了偶数次(包含\(0\)),则该元素的取值等于其初始值。由此,我们可以想到用树状数组维护每个数被修改的次数,最后统计其奇偶性即可。具体见代码。
参考代码如下:
#include <cstdio>
using namespace std;
#define maxn 100005
int n, m;
int c[maxn];
int lowbit(int x)
{
return x & (-x);
}
void add(int x, int y)
{
for (int i = x; i <= n; i += lowbit(i))
c[i] += y;
}
void update(int l, int r)
{
add(l, 1);
add(r + 1, -1);
}
int query(int x)
{
int sum = 0;
for (int i = x; i; i -= lowbit(i))
sum += c[i];
return sum;
}
int main()
{
int op, l, r;
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
scanf("%d", &op);
if (op == 1)
{
scanf("%d%d", &l, &r);
update(l, r);
}
else
{
scanf("%d", &l);
printf("%d\n", query(l) % 2);
}
}
return 0;
}
种类总数问题
给定一个长度为\(n\)的序列,每次可以给序列\([l_i, r_i]\)的范围内都打上一种新的标记,区间\([l_i, r_i]\)称为标记区间或是询问\([l_i, r_i]\)位置内有多少种不同的标记。请维护上述内容。
使用一个比较脑洞的小规律:设\(tl_{i}\)为\(1\)到\(i\)中,有多少个\(l_i\);\(tr_i\)为\(1\)到\(i\)中,有多少个\(r_i\)。显然,一个区间\([l, r]\)中的不同总数等于\(tl_r - tl_{l - 1}\)。即,所有起点在\([l, r]\)中的标记区间都可能覆盖\([l, r]\)。用它减去右端点小于\(l\),即与\([l, r]\)不相交(在其前面)的标记区间总数,就是覆盖了\([l, r]\)的标记区间总数,即\([l, r]\)内的不同标记总数。
参考代码
#include <cstdio>
using namespace std;
#define maxn 50005
int n, m;
int tl[maxn], tr[maxn];
int lowbit(int x)
{
return x & (-x);
}
void add(int t[], int p)
{
for (int i = p; i <= n; i += lowbit(i))
t[i]++;
}
void update(int l, int r)
{
add(tl, l);
add(tr, r);
}
int query(int t[], int p)
{
int sum = 0;
for (int i = p; i; i -= lowbit(i))
sum += t[i];
return sum;
}
int getSum(int l, int r)
{
return query(tl, r) - query(tr, l - 1);
}
int main()
{
int k, l, r;
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d", &k, &l, &r);
if (k == 1)
update(l, r);
else
printf("%d\n", getSum(l, r));
}
return 0;
}
\(dfs\) 序
给定一棵树,每次可以给树的某个节点增加一个权值,或查询某棵子树的权值之和。试用树状数组维护。
显然,在 \(dfs\) 序中,一个结点和它的子树是连续的。我们可以利用这一性质,设结点 \(i\) 的子树起点为 \(l_i\) ,终点为 \(r_i\) 。则 \(i\) 的子树权值之和为 \(\sum\limits_{k = l_i}^{r_i} w_i\) 。每次给结点 \(i\) 增加权值 \(w\) ,我们只需要在树状数组中 \(dfs_i\) 的位置加上 \(w\) 即可。
参考代码
#include <cstdio>
using namespace std;
const int maxn = 1e6 + 5;
const int maxm = 2e6 + 5;
struct node
{
int to, nxt;
}edge[maxm];
int n, m, root, cnt, tot;
int w[maxn], head[maxn], l[maxn], r[maxn];
long long c[maxn];
int lowbit(int x)
{
return x & (-x);
}
void add(int p, int x)
{
for (int i = p; i <= n; i += lowbit(i))
c[i] += x;
}
long long query(int p)
{
long long sum = 0;
for (int i = p; i; i -= lowbit(i))
sum += c[i];
return sum;
}
void add_edge(int u, int v)
{
cnt++;
edge[cnt].to = v;
edge[cnt].nxt = head[u];
head[u] = cnt;
}
void dfs(int u, int fa)
{
l[u] = ++tot;
for (int i = head[u]; i; i = edge[i].nxt)
if (edge[i].to != fa)
dfs(edge[i].to, u);
r[u] = tot;
}
int main()
{
int op, u, v;
scanf("%d%d%d", &n, &m, &root);
for (int i = 1; i <= n; i++)
scanf("%d", &w[i]);
for (int i = 1; i <= n - 1; i++)
{
scanf("%d%d", &u, &v);
add_edge(u, v);
add_edge(v, u);
}
dfs(root, -1);
for (int i = 1; i <= n; i++)
add(l[i], w[i]);
for (int i = 1; i <= m; i++)
{
scanf("%d%d", &op, &u);
if (op == 1)
{
scanf("%d", &v);
add(l[u], v);
}
else
printf("%lld\n", query(r[u]) - query(l[u] - 1));
}
return 0;
}
标签:树状,int,sum,times,maxn,数组 来源: https://www.cnblogs.com/Ling-Lover/p/binary-indexed-tree.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。