线段树算法讲解

线段树算法讲解

问:给你一个长度为n的数组,然后询问m次,每次询问是给定两个数x,y。求在【x,y】区间的数之和。
也许你会说遍历,但若n为1e6,m为1e3呢?这是可以用一种数据结构——线段树。
线段树的每一个点维护一个[l,r)的区间。长这样:
在这里插入图片描述图中,数组长度为6,第一个节点维护的是【1,6】的最大值,左节点维护的是【1,3】的最大值,以此类推,直至叶子节点。
下面,以求区间和为例来进一步说明。
定义:

const int maxn = 1e5 + 10;
struct node{
	int l, r;//左右节点 
	int data;//存储的值 
	int mid()
	{
		return (l + r) >> 1;
	}
}sum[maxn << 2];//一般开四倍空间 

线段树的构建
void build(int root, int l, int r)
{
	sum[root].l = l, sum[root].r = r;//给每个节点所维护的区间赋值 
	if(sum[root].l == sum[root].r)//到达叶子节点 
	{
		sum[root].data = a[sum[root].l];
		return;
	}
	int mid = sum[root].mid();
	build(root << 1, l, mid);//左右递归建树 
	build(root << 1 | 1, mid + 1, r);
	push_up(root);//精髓所在 
}

观察代码,可以发现,线段树的构建是不断地递归直至叶子节点赋值。叶子节点赋值之后用push_up函数为其父亲节点赋值。

push_up函数
void push_up(int root)
{
	sum[root].data = sum[root << 1].data + sum[root << 1 | 1].data;
}
单点修改
void update(int root, int p, int v)//p位置的值加v 
{
	if(sum[root].l == sum[root].r)//找到需要修改的节点 
	{
		sum[root].data += v;
		return;
	}
	int mid = sum[root].mid();
	if(p <= mid)//若在左子节点内 
		update(root << 1, p, v);
	else if(p > mid)//若在右子节点内 
		update(root << 1 | 1, p, v);
	push_up(root);//更新父节点 
}
单点修改情况下的查询
int query(int root, int l, int r)
{
	int ans = 0;
	if(sum[root].l >= l && sum[root].r <= r)
	{
		return sum[root].data;
	}
	int mid = sum[root].mid();
	if(mid >= l)//若所查询区间和左子节点区间有交集 
		ans += query(root << 1, l, r);
	if(mid + 1 <= r)//若和右子节点区间有交集 
		ans += query(root << 1 | 1, l, r);
	return ans;
}

上面是单点修改的情况,下面讲解区间修改
在对某个全进修改时需要用到lazy标记。当使用lzay标记某个区间时,此区间之上的区间显示的内容是修改过的,但其子区间没有被修改,当需要用到子区间时会通过push_down函数进行修改,这样做主要是为了减少时间复杂度。

void update(int root, int l, int r, int v)//[l, r]区间的值更改为v 
{
	if(sum[root].l >= l && sum[root].r <= r)//当前节点区间完全子啊需修改区间内 
	{
		sum[root].data = v * (sum[root].r - sum[root].l + 1);
		lazy[root] = v;
		return;
	}
	push_down(root);//查询是否有lazy标记 
	int mid = sum[root].mid();
	if(mid >= l)//与做区间有交集 
		update(root << 1, l, r, v);
	if(mid + 1 <= r)//与有区间有交集 
		update(root << 1 | 1, l, r, v);
	push_up(root);
}

push_down函数

void push_down(int root)
{
	if(lazy[root])
	{
		//lazy标记下移 
		lazy[root << 1] = lazy[root]; 
		lazy[root << 1 | 1] = lazy[root];
		//更新左右子节点区间 
		sum[root << 1].data = lazy[root] * (sum[root << 1].r - sum[root << 1].l + 1);
		sum[root << 1 | 1].data = lazy[root] * (sum[root << 1 | 1].r - sum[root << 1 | 1].l + 1);
		lazy[root] = 0;//已经下移,归零 
	}
}

有lazy标记的查询区间

int query(int root, int l, int r)//查询[l, r] 区间 
{
	int ans = 0;
	if(sum[root].l >= l && sum[root].r <= r)
	{
		return sum[root].data;
	}
	push_down(root);//查询lazy标记 
	int mid = sum[root].mid();
	if(mid >= l)
		ans += query(root << 1, l, r);
	if(mid + 1 <= r)
		ans += query(root << 1 | 1, l, r);
	return ans;
}

附上完整的单点修改和区间修改代码:
单点修改

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#define mem(a, b) memset(a, b, sizeof(a))
using namespace std;

const int maxn = 1e5 + 10;
struct node{
	int l, r;//左右节点 
	int data;//存储的值 
	int mid()
	{
		return (l + r) >> 1;
	}
}sum[maxn << 2];//一般开四倍空间 

int a[maxn];
int n;
char str[10];

void init()
{
	mem(a, 0);
	mem(sum, 0);
}

void push_up(int root)
{
	sum[root].data = sum[root << 1].data + sum[root << 1 | 1].data;
}

void build(int root, int l, int r)
{
	sum[root].l = l, sum[root].r = r;//给每个节点所维护的区间赋值 
	if(sum[root].l == sum[root].r)//到达叶子节点 
	{
		sum[root].data = a[sum[root].l];
		return;
	}
	int mid = sum[root].mid();
	build(root << 1, l, mid);//左右递归建树 
	build(root << 1 | 1, mid + 1, r);
	push_up(root);//精髓所在 
}

void update(int root, int p, int v)//p位置的值加v 
{
	if(sum[root].l == sum[root].r)//找到需要修改的节点 
	{
		sum[root].data += v;
		return;
	}
	int mid = sum[root].mid();
	if(p <= mid)//若在左子节点内 
		update(root << 1, p, v);
	else if(p > mid)//若在右子节点内 
		update(root << 1 | 1, p, v);
	push_up(root);//更新父节点 
}

int query(int root, int l, int r)
{
	int ans = 0;
	if(sum[root].l >= l && sum[root].r <= r)
	{
		return sum[root].data;
	}
	int mid = sum[root].mid();
	if(mid >= l)//若所查询区间和左子节点区间有交集 
		ans += query(root << 1, l, r);
	if(mid + 1 <= r)//若和右子节点区间有交集 
		ans += query(root << 1 | 1, l, r);
//	if(mid >= r)
//		ans += query(root << 1, l, r); 
//	else if(mid < l)
//		ans += query(root << 1 | 1, l, r);
//	else 
//	{
////		ans += query(root << 1, l, mid);
////		ans += query(root << 1 | 1, mid + 1, r);
//		ans += query(root << 1, l, r);
//		ans += query(root << 1 | 1, l, r);
//	}
	return ans;
}

int main()
{
	int t;
	scanf("%d", &t);
	int num = 1;
	while(t--)
	{
		init();
		scanf("%d", &n);
		for(int i = 1; i <= n; i++)
			scanf("%d", &a[i]);
		build(1, 1, n);
//		for(int i = 1; i <= n; i++)
//		{
//			cout << i << ":" << sum[i].l << " " << sum[i].r << " " << sum[i].data << endl;
//		}
		printf("Case %d:\n", num++);
		while(scanf("%s", str))
		{
//			cout << str << endl;
//		for(int i = 1; i <= n; i++)
//		{
//			cout << i << ":" << sum[i].l << " " << sum[i].r << " " << sum[i].data << endl;
//		}
//		cout << endl;
			if(str[0] == 'E')	break;
			int x, y;
			scanf("%d%d", &x, &y);
			if(str[0] == 'A')
			{
				update(1, x, y);
			}
				
			else if(str[0] == 'S')
				update(1, x, -y);
			else if(str[0] == 'Q')
//				cout << query(1, x, y) << endl;
			printf("%d\n", query(1, x, y));
		}
	}
	return 0;
}

区间修改

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#define mem(a, b) memset(a, b, sizeof(a))
using namespace std;

const int maxn = 5e5;
struct node{
	int l, r;
	int data;
	int mid()
	{
		return (l + r) >> 1;
	}
}sum[maxn << 2];

//int a[maxn];
int lazy[maxn];
int n;
//char str[10];

void init()
{
//	mem(a, 0);
	mem(sum, 0);
	mem(lazy, 0);
}

void push_up(int root)
{
	sum[root].data = sum[root << 1].data + sum[root << 1 | 1].data;
}

void build(int root, int l, int r)
{
	sum[root].l = l, sum[root].r = r;
	if(sum[root].l == sum[root].r)
	{
		
//		sum[root].data = a[sum[root].l];
		sum[root].data = 1;
		return;
	}
	int mid = sum[root].mid();
	build(root << 1, l, mid);
	build(root << 1 | 1, mid + 1, r);
	push_up(root);
}

//void update(int root, int p, int v)
//{
//	if(sum[root].l == sum[root].r)
//	{
//		sum[root].data += v;
//		return;
//	}
//	int mid = sum[root].mid();
//	if(p <= mid)
//		update(root << 1, p, v);
//	else if(p > mid)
//		update(root << 1 | 1, p, v);
//	push_up(root);
//}

void push_down(int root)
{
	if(lazy[root])
	{
		//lazy标记下移 
		lazy[root << 1] = lazy[root]; 
		lazy[root << 1 | 1] = lazy[root];
		//更新左右子节点区间 
		sum[root << 1].data = lazy[root] * (sum[root << 1].r - sum[root << 1].l + 1);
		sum[root << 1 | 1].data = lazy[root] * (sum[root << 1 | 1].r - sum[root << 1 | 1].l + 1);
		lazy[root] = 0;//已经下移,归零 
	}
}

void update(int root, int l, int r, int v)//[l, r]区间的值更改为v 
{
	if(sum[root].l >= l && sum[root].r <= r)//当前节点区间完全子啊需修改区间内 
	{
		sum[root].data = v * (sum[root].r - sum[root].l + 1);
		lazy[root] = v;
		return;
	}
	push_down(root);//查询是否有lazy标记 
	int mid = sum[root].mid();
	if(mid >= l)//与做区间有交集 
		update(root << 1, l, r, v);
	if(mid + 1 <= r)//与有区间有交集 
		update(root << 1 | 1, l, r, v);
	push_up(root);
}

int query(int root, int l, int r)//查询[l, r] 区间 
{
	int ans = 0;
	if(sum[root].l >= l && sum[root].r <= r)
	{
		return sum[root].data;
	}
	push_down(root);//查询lazy标记 
	int mid = sum[root].mid();
	if(mid >= l)
		ans += query(root << 1, l, r);
	if(mid + 1 <= r)
		ans += query(root << 1 | 1, l, r);
//	if(mid >= r)
//		ans += query(root << 1, l, r); 
//	else if(mid < l)
//		ans += query(root << 1 | 1, l, r);
//	else 
//	{
////		ans += query(root << 1, l, mid);
////		ans += query(root << 1 | 1, mid + 1, r);
//		ans += query(root << 1, l, r);
//		ans += query(root << 1 | 1, l, r);
//	}
	return ans;
}

int main()
{
	int t;
	scanf("%d", &t);
	int num = 1;
	while(t--)
	{
		init();
		scanf("%d", &n);
		int m;
//		for(int i = 1; i <= n; i++)
//			scanf("%d", &a[i]);
		build(1, 1, n);
//		for(int i = 1; i <= n; i++)
//		{
//			cout << i << ":" << sum[i].l << " " << sum[i].r << " " << sum[i].data << endl;
//		}
		
		scanf("%d", &m);
		for(int i = 0; i < m; i++)
		{
			int x, y, z;
			scanf("%d%d%d", &x, &y, &z);
			update(1, x, y, z);
		}
		printf("Case %d: The total value of the hook is %d.\n", num++, sum[1].data);
//		cout << sum[1].data << endl;
	}
	return 0;
}

OK,上课去了,,应该还来得及。。

发布了73 篇原创文章 · 获赞 15 · 访问量 8116

猜你喜欢

转载自blog.csdn.net/ln2037/article/details/100565241
今日推荐