线段树


线段树

具体例题

区间加法和区间求和: 记录详情 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

区间加法乘法,求和 记录详情 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

基本概念

1、线段树是一棵二叉搜索树,它储存的是一个区间的信息。

2、每个节点以结构体的方式存储,结构体包含以下几个信息:

区间左端点、右端点;(这两者必有)

这个区间要维护的信息(事实际情况而定,数目不等)。

3、线段树的基本思想:二分

形式如下:

结构体------要开4倍的空间

struct node
{
	long long  sum, add;
	int left, right;
};//左右节点,区间和,懒标记,
或者
struct node
{
	long long sum, add, mul;
	int left, right;
}; //支持乘法操作,mul(乘法的懒标记)

线段树支持的操作有:

1,建树,即建立一棵线段树

//create(1,n,1);//将n个数建立成线段树
void create(int l, int r, int i)
{
	a[i].left = l;
	a[i].right = r;
	a[i].sum = 0;
	a[i].add = 0;
	if (l == r)
	{
		cin >> a[i].sum;
		return;
	}
	int mid = (l + r) / 2;
	create(l, mid, i << 1);
	create(mid + 1, r, (i << 1) + 1);
	a[i].sum = a[i << 1].sum + a[(i << 1) + 1].sum;
}

2,单点查询,即查询一个点的状态,设待查询点为x

int query(int x, int i)//x为要查询的第x个数
{
	if (a[i].left == a[i].right && x == a[i].left)
	{
		return a[i].sum;
	}
	push_down(i);//区间被修改时使用
	int mid = (a[i].left + a[i].right) / 2;
	if (x <= mid)
query(x, i << 1);
	else
		query(x, i << 1 | 1);
}

3、单点修改,即更改某一个点的状态,即对第x个数加上y

void update(int i, int x, int y)让第x个数加上y
{
	if (a[i].left == a[i].right && a[i].left == x)
	{
		a[i].sum += y;
		return;
	}
	push_down(i); 
	int mid = (a[i].left + a[i].right) / 2;
	if (x <= mid)
		update(i << 1, x, y);
	else
		update(i << 1 | 1, x, y);
	a[i].sum = a[i << 1].sum + a[i << 1 | 1].sum;//向上回溯
}

4、区间查询,即查询一段区间的状态

void query(int l, int r, int i) //查询[l,r]区间的和
//ans为全局变量
{
	if (a[i].left == l && a[i].right == r)
	{
		ans += a[i].sum;
		return;
	}
	push_down(i);
	int mid = (a[i].left + a[i].right) / 2;
	if (l > mid)
		query(l, r, (i << 1) + 1);
	else if (r <= mid)
		query(l, r, i << 1);
	else
	{
		query(l, mid, i << 1);
		query(mid + 1, r, (i << 1) + 1);
	}
}

5、区间修改,即修改一段连续区间的值

void update(int l, int r, int i, long long k) //区间[l,r]所有数加上k
{
	if (a[i].left == l && a[i].right == r)
	{
		a[i].sum += k * (r - l + 1);
		a[i].add += k;
		return;
	}
	push_down(i); 
	int mid = (a[i].left + a[i].right) / 2;
	if (r <= mid)
		update(l, r, i << 1, k);
	else if (l > mid)
		update(l, r, (i << 1) + 1, k);
	else
	{
		update(l, mid, i << 1, k);
		update(mid + 1, r, (i << 1) + 1, k);
	}
	a[i].sum = a[i << 1].sum + a[(i << 1) + 1].sum;
}

懒标记下传函数

void push_down(int i) 
{
	if (a[i].add != 0)
	{
		a[i << 1].add += a[i].add;
		a[(i << 1) + 1].add += a[i].add;
a[i << 1].sum += a[i].add * (a[i << 1].right - a[i << 1].left + 1);
		a[(i << 1) + 1].sum += a[i].add * (a[(i << 1) + 1].right - a[(i << 1) + 1].left + 1);
		a[i].add = 0;
	}
}
区间加法和乘法混合时;

区间乘上x时,乘法懒标记mui*=x;加法懒标记add*=x;
区间加上x时,加法懒标记add+=x;

向下传递时:push_down(int i)
先计算乘法,后计算加法,最后add清零,mul变成1