在对一个连续的序列进行一定的修订,查询操作时,我们可以构建树状结构。
我们有两个选择,线段树和树状数组
线段树
我们可以构建一棵二叉树,将序列的左右不断左右分割,并且不断构建二叉树。
逻辑上的形状:
同时,利用PushUp来完成对已有数据的统计
void PushUP(int now)
{
tree[now] = tree[now * 2] + tree[now * 2 + 1];
}
void build(int l, int r, int now)
{
if(l == r)
{
scanf("%d", &tree[now]);
return ;
}
int m = (l + r) / 2;
build(l, m, now * 2);
build(m + 1, r, now * 2 + 1);
PushUP(now);
}
在更新时,我们需要不断的判断当前的 l,r(左右边界)是否为所求,找到并更改后再次利用PushUP完成父节点与祖节点的更新
void update(int l, int r, int now, int p, int x)
{
if(l == r)
{
tree[now] += x;
return ;
}
int m = (l + r) / 2;
if(p <= m)
update(l, m, now * 2, p, x);
else
update(m + 1, r, now * 2 + 1, p, x);
PushUP(now);
}
在查询时,我们同样需要判断当前 l,r是否在L,R(起点与终点),如果在,返回当前节点的值就好,最后将得到的是进行相加
int query(int l, int r, int now, int L, int R)
{
if(L <= l && r <= R)
return tree[now];
int m = (l + r) / 2;
int sum = 0;
if(L <= m)
sum += query(l, m, now * 2, L, R);
if(R > m)
sum += query(m + 1, r, now * 2 + 1, L, R);
return sum;
}
完整代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN = 55555;
int tree[MAXN * 4 + 1];
void PushUP(int now)
{
tree[now] = tree[now * 2] + tree[now * 2 + 1];
}
void build(int l, int r, int now)
{
if(l == r)
{
scanf("%d", &tree[now]);
return ;
}
int m = (l + r) / 2;
build(l, m, now * 2);
build(m + 1, r, now * 2 + 1);
PushUP(now);
}
void update(int l, int r, int now, int p, int x)
{
if(l == r)
{
tree[now] += x;
return ;
}
int m = (l + r) / 2;
if(p <= m)
update(l, m, now * 2, p, x);
else
update(m + 1, r, now * 2 + 1, p, x);
PushUP(now);
}
int query(int l, int r, int now, int L, int R)
{
if(L <= l && r <= R)
return tree[now];
int m = (l + r) / 2;
int sum = 0;
if(L <= m)
sum += query(l, m, now * 2, L, R);
if(R > m)
sum += query(m + 1, r, now * 2 + 1, L, R);
return sum;
}
int main()
{
int T;
scanf("%d", &T);
for(int cas = 1; cas <= T; cas++)
{
printf("Case %d:\n", cas);
int n;
scanf("%d", &n);
build(1, n, 1);
char op[10];
int p, x;
while(~scanf("%s", op))
{
if(op[0] == 'E')
break;
else
{
scanf("%d%d", &p, &x);
if(op[0] == 'A')
update(1, n, 1, p, x);
else if(op[0] == 'S')
update(1, n, 1, p, -x);
else if(op[0] == 'Q')
printf("%d\n", query(1, n, 1, p, x));
}
}
}
return 0;
}
树状数组,
这个就很神奇了,它也是构造一课树,一颗歪脖树
上图:
它的父节点不同于线段树的,它的父节点可以连接着多个子节点,,每个子节点可能链接一个节点,也有可能链接多个节点,么给父节点同样是子节点的和。
在这里,父节点的寻找很重要, 因此我们需要 lowbit 函数
int lowbit(int x)
{
return x & -x;
}
明明这么短,可是没它真的不行。
单点更新,更新,在这里我们可以从需要更改的开始,不断更新它的父节点 , +lowbit(x)就是找当前节点的父节点的方法
void update(int x, int y)
{
for(int i = x; i <= n; i += lowbit(i))
tree[i] += y;
}
查询操作,这个略显复杂,通过查找起点和终点的值相减来完成 -lowbit(x)是寻找上一个父节点
int query(int x)
{
int ans = 0;
for(int i = x; i > 0; i -= lowbit(i))
ans += tree[i];
return ans;
}
int a, b;
scanf("%d %d", &a, &b);
printf("%d\n", query(b) - query(a - 1));
完整代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
const int MAXN = 50005;
int tree[MAXN];
int num[MAXN];
int n;
int lowbit(int x)
{
return x & -x;
}
void update(int x, int y)
{
for(int i = x; i <= n; i += lowbit(i))
tree[i] += y;
}
int query(int x)
{
int ans = 0;
for(int i = x; i > 0; i -= lowbit(i))
ans += tree[i];
return ans;
}
int main()
{
int T;
scanf("%d", &T);
for(int cas = 1; cas <= T; cas++)
{
scanf("%d", &n);
memset(tree, 0, sizeof(tree));
memset(num, 0, sizeof(num));
for(int i = 1; i <= n; i++)
{
scanf("%d", &num[i]);
update(i, num[i]);
}
printf("Case %d:\n", cas);
int a, b;
char op[10];
while(~scanf("%s", op))
{
if(op[0] == 'E')
break;
else
{
scanf("%d %d", &a, &b);
if(op[0] == 'A')
update(a, b);
else if(op[0] == 'S')
update(a, -b);
else if(op[0] == 'Q')
printf("%d\n", query(b) - query(a - 1));
}
}
}
return 0;
}
至于线段树区间合并,树状数组区间更新,还没学会......