线段树与树状数组 (单点更新, 区间查找)

在对一个连续的序列进行一定的修订,查询操作时,我们可以构建树状结构。

我们有两个选择,线段树和树状数组

线段树

我们可以构建一棵二叉树,将序列的左右不断左右分割,并且不断构建二叉树。

逻辑上的形状:

同时,利用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;
}

至于线段树区间合并,树状数组区间更新,还没学会......

猜你喜欢

转载自blog.csdn.net/someone_and_anyone/article/details/81099329