1 模板代码
lowbit
按位与运算是指将两个数的二进制表示进行按位与操作,即对应位上的数字都为1时结果为1,否则为0。
在C++中,按位与运算符号为&,使用按位与运算进行输入输出时,输入输出的数都是以十进制形式表示的。
int lowbit(int x)
{
return x & -x;
}
单点更新
void update(int i)
{
while(i<=n)
{
c[i] += 1;
i += lowbit(i);
}
}
单点查询
int sum(int i)
{
int ret = 0;
while(i>0)
{
ret += c[i];
i -= lowbit(i);
}
return ret;
}
区间更新
void update(int i)
{
while(i<=n)
{
c[i] += 1;
i += lowbit(i);
}
}
void range_update(int x, int y)
{
update(x, 1);
update(y+1, -1);
}
前缀和区间更新
void update(int p, int val) {
for(int i=p; i<=n; i+=i&-i) {
c1[i] += val;
c2[i] += val * p;
}
}
void range_update(int x, int y, int z) {
update(x, z);
update(y+1, -z);
}
前缀和单点查询
公式推导
long long int sum(int p) {
long long int res = 0;
for(int i=p; i; i-=i&-i)
res += (p+1)*c1[i] - c2[i];
return res;
}
2 Example
2.1 B 敌兵布阵
思路:读入时单点更新,每次操作时单点更新,每次输出时单点查询。
参考代码
#include<bits/stdc++.h>
using namespace std;
int t, n, a, c[50010], x, y;
char s[10];
int lowbit(int x)
{
return x & -x;
}
void update(int i, int val)
{
while(i<=n)
{
c[i] += val;
i += lowbit(i);
}
}
int sum(int i)
{
int ret = 0;
while(i>0)
{
ret += c[i];
i -= lowbit(i);
}
return ret;
}
int main() {
scanf("%d", &t);
for(int j=1; j<=t; j++)
{
printf("Case %d:\n", j);
memset(c, 0, sizeof(c));
scanf("%d", &n);
for(int i=1; i<=n; i++)
{
scanf("%d", &a);
update(i, a);
}
while(scanf("%s", &s), s[0]!='E')
{
scanf("%d %d", &x, &y);
if(s[0]=='A')
update(x, y);
else if(s[0]=='S')
update(x, -y);
else if(s[0]=='Q')
printf("%d\n", sum(y)-sum(x-1));
}
}
return 0;
}
2.2 C 交换的次数(1)
思路:每次读入时单点更新update(x, 1),接着对数字对应树状数组下标至末位进行区间查询,累加入ans中。
参考代码
#include<bits/stdc++.h>
using namespace std;
int n, a, c[100010];
int lowbit(int x)
{
return x & -x;
}
void update(int i)
{
while(i<=n)
{
c[i] += 1;
i += lowbit(i);
}
}
int sum(int i)
{
int ret = 0;
while(i>0)
{
ret += c[i];
i -= lowbit(i);
}
return ret;
}
int main()
{
while(scanf("%d", &n)!=EOF)
{
long long int ans = 0;
memset(c, 0, sizeof(c));
for(int i=1; i<=n; i++)
{
scanf("%d", &a);
update(a);
ans += (sum(n)-sum(a));
}
printf("%lld\n", ans);
}
return 0;
}
2.3 D Color the ball
思路:区间更新+ (从1-n) 单点查询
参考代码
#include<bits/stdc++.h>
using namespace std;
int n, c[100010];
void update(int p, int val)
{
while(p<=n)
{
c[p] += val;
p += p & -p;
}
}
void range_add(int x, int y)
{
update(x, 1);
update(y+1, -1);
}
int sum(int i)
{
int res = 0;
while(i)
{
res += c[i];
i -= i & -i;
}
return res;
}
int main()
{
while(scanf("%d", &n)!=EOF, n!=0)
{
int x, y;
memset(c, 0, sizeof(c));
for(int i=0; i<n; i++)
{
scanf("%d %d", &x, &y);
range_add(x, y);
}
for(int i=1; i<=n; i++)
printf("%d ", sum(i));
printf("\n");
}
return 0;
}
2.4 F 张煊的金箍棒(3)
思路:前缀和区间更新+前缀和区间查询。
参考代码
#include<bits/stdc++.h>
using namespace std;
long long int c1[100100], c2[100100];
int n;
void update(int p, int val) {
for(int i=p; i<=n; i+=i&-i) {
c1[i] += val;
c2[i] += val * p;
}
}
void range_update(int x, int y, int z) {
update(x, z);
update(y+1, -z);
}
long long int sum(int p) {
long long int res = 0;
for(int i=p; i; i-=i&-i)
res += (p+1)*c1[i] - c2[i];
return res;
}
int main() {
int t;
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
int m, q, x, y, z, a, b;
memset(c1, 0, sizeof(c1));
memset(c2, 0, sizeof(c2));
update(1, 1);
scanf("%d %d", &m, &q);
for(int j=0; j<m; j++) {
scanf("%d %d %d", &x, &y, &z);
range_update(x, y, z);
}
for(int j=0; j<q; j++) {
scanf("%d %d", &a, &b);
long long int ans = sum(b)-sum(a-1);
printf("%lld\n", ans);
}
}
return 0;
}
(还是一个非常小白的错误)记得数据较大时开long long否则会WA50%.
3 总结
树状数组主要解决区间更新/区间查询或其组合的问题,将o(n)级别的运算简化为o(logn)级别的运算,在处理较大数据时很有帮助,且代码模板简单,多数题直接套模板即可。