首页 > 其他分享 >note 线段树

note 线段树

时间:2023-10-04 16:24:23浏览次数:33  
标签:rt cur int 线段 tree times note 节点

适用场景:不断区间修改、区间询问。

假设我们要区间求和,\(tree\) 的含义:区间的和,其两个子节点为这个区间分为两半的和。

我们把一个数组 \(a\) 看作一颗树 \(tree\),例:

1 1 2 3 3 3

对应的 \(tree\)(\(()\)里是编号,\([]\)里是对应的区间):

             (1)13[1,6]
            /           \
   (2)4[1,3]             (3)9[4,6]
     /      \               /     \
 (4)2[1,2] (5)2[3,3] (6)6[4,5]     (7)3[6,6]
    /     \             /      \
 (8)1[1,1] (9)1[2,2] (12)3[4,4] (13)3[5,5]

初始要先建树


假如现在询问区间 \([3,5]\) 的和:

先到 \(1\) 号根节点:发现并没有全都不属于它,也没有都属于它,因此询问它的子节点 \(1 \times 2\) 和 \(1 \times 2 +1\) 寻找(\(2\) 和 \(3\))

接下来到 \(2\) 号节点:发现并没有全都不属于它,也没有都属于它,因此询问它的子节点 \(2 \times 2\) 和 \(2 \times 2 +1\) 寻找(\(4\) 和 \(5\))

接下来到 \(4\) 号节点:发现全都不属于它,直接跳过。

接下来到 \(5\) 号节点:发现都属于它,答案直接 \(+tree[5]\)(2)。

接下来到 \(3\) 号节点:发现并没有全都不属于它,也没有都属于它,因此询问它的子节点 \(3 \times 2\) 和 \(3 \times 2 +1\) 寻找(\(6\) 和 \(7\))

接下来到 \(6\) 号节点:发现都属于它,答案直接 \(+tree[6]\)(6)。

接下来到 \(7\) 号节点:发现全都不属于它,直接跳过。


这样一来,最终的答案就是 \(tree[5]+tree[6]=2+6=8\)。

验证:\(2+3+3=8\)。


假如使 \([2,6]\) 的元素全部加 \(3\)。(未优化)

先到 \(1\) 号根节点:发现有部分属于它,因此 tree[1]+=(6-2+1)*3,由于要接着更新,因此继续更新它的子节点 \(1 \times 2\) 和 \(1 \times 2 +1\) 去更新(\(2\) 和 \(3\))

接着到 \(2\) 号节点:发现有部分属于它,因此 tree[2]+=(3-2+1)*3,由于要接着更新,因此继续更新它的子节点 \(2 \times 2\) 和 \(2 \times 2 +1\) 去更新(\(4\) 和 \(5\))

接着到 \(4\) 号节点:发现有部分属于它,因此 tree[4]+=(2-2+1)*3,由于要接着更新,因此继续更新它的子节点 \(4 \times 2\) 和 \(4 \times 2 +1\) 去更新(\(8\) 和 \(9\))

接着到 \(8\) 号节点:发现没有部分属于它,因此退出。

接着到 \(9\) 号节点:发现有部分属于它,因此 tree[9]+=(2-2+1)*3,由于是叶子节点,退出。

接着到 \(5\) 号节点:发现有部分属于它,因此 tree[5]+=(3-3+1)*3,由于是叶子节点,退出。

接着到 \(3\) 号节点:发现有部分属于它,因此 tree[3]+=(6-4+1)*3,由于要接着更新,因此继续更新它的子节点 \(3 \times 2\) 和 \(3 \times 2 +1\) 去更新(\(6\) 和 \(7\))

接着到 \(6\) 号节点:发现有部分属于它,因此 tree[6]+=(5-4+1)*3,由于要接着更新,因此继续更新它的子节点 \(6 \times 2\) 和 \(6 \times 2 +1\) 去更新(\(12\) 和 \(13\))

接着到 \(12\) 号节点:发现有部分属于它,因此 tree[12]+=(4-4+1)*3,由于是叶子节点,退出。

接着到 \(13\) 号节点:发现有部分属于它,因此 tree[13]+=(5-5+1)*3,由于是叶子节点,退出。

接着到 \(7\) 号节点:发现有部分属于它,因此 tree[7]+=(6-6+1)*3,由于是叶子节点,退出。


最终:

             (1)28[1,6]
            /           \
   (2)10[1,3]             (3)18[4,6]
     /      \               /     \
 (4)5[1,2] (5)5[3,3] (6)15[4,5]     (7)6[6,6]
    /     \             /      \
 (8)1[1,1] (9)4[2,2] (12)6[4,4] (13)6[5,5]

这样一来,时间复杂度达到 \(O(n \log n)\),还不如直接枚举。

我们先看这一部分的代码:

#include<bits/stdc++.h>
using namespace std;

void pushup(int cur)
{
	tree[cur]=tree[2*cur]+tree[2*cur+1];
	return ;
}

void build(int cur,int lt,int rt) //建树 
{
	if(lt==rt)
	{
		tree[cur]=a[lt];
		return ;
	}
	int mid=(lt+rt)>>1;
	build(2*cur,lt,mid);
	build(2*cur+1,mid+1,rt);
	pushup(cur);
	return ;
}

int query(int cur,int lt,int rt,int qx,int qy) //询问 
{
	if(rt<qx||qy<lt)
		return 0;
	if(qx<=lt&&rt<=qy)
		return tree[cur];
	int mid=(lt+rt)>>1;
	return query(2*cur,lt,mid,qx,qy)+query(2*cur+1,mid+1,rt,qx,qy);
}

void update(int cur,int lt,int rt,int qx,int qy,int val) //修改 
{
	if(rt<qx||qy<lt)
		return ;
	if(lt==rt)
	{
		tree[cur]+=val;
		return ;
	}
	int mid=(lt+rt)>>1;
	update(2*cur,lt,mid,qx,qy,val);
	update(2*cur+1,mid+1,rt,qx,qy,val);
	pushup(cur);
	return ;
}

线段树的优化:

我们发现修改的时候时间太长了,因此需要优化。

优化方法:打懒标记:

  1. 如果这个点一直在被更新而没有被询问,就会浪费很多的时间,因此打懒标记表示这里有 \(val\) 的值没有算。

  2. 标记下传:如果递归到这个点了,那么就要将这个点的懒标记打给他的子节点。

这样一来,询问的时间复杂度降到 \(O(\log n)\)。

接下来我们模拟一下:


假如使 \([2,6]\) 的元素全部加 \(3\)。(优化)

先到 \(1\) 号根节点:发现有部分属于它,接着更新它的子节点 \(1 \times 2\) 和 \(1 \times 2 +1\) 去更新(\(2\) 和 \(3\)),最终 tree[1]=tree[2]+tree[3]

接着到 \(2\) 号节点:发现有部分属于它,接着更新它的子节点 \(2 \times 2\) 和 \(2 \times 2 +1\) 去更新(\(4\) 和 \(5\)),最终 tree[2]=tree[4]+tree[5]

接着到 \(4\) 号节点:发现有部分属于它,接着更新它的子节点 \(4 \times 2\) 和 \(4 \times 2 +1\) 去更新(\(8\) 和 \(9\)),最终 tree[4]=tree[8]+tree[9]

接着到 \(8\) 号节点:发现没有部分属于它,因此退出。

接着到 \(9\) 号节点:发现全属于它,因此 tree[9]+=(2-2+1)*3,tag[9]+=3,退出。

接着到 \(5\) 号节点:发现全属于它,因此 tree[5]+=(3-3+1)*3,tag[5]+=3,退出。

接着到 \(3\) 号节点:发现全属于它,因此 tree[3]+=(6-4+1)*3,tag[3]+=3,退出。

再询问区间 \([3,5]\) 的和:

先到 \(1\) 号根节点:发现并没有全都不属于它,也没有都属于它,因此询问它的子节点 \(1 \times 2\) 和 \(1 \times 2 +1\) 寻找(\(2\) 和 \(3\))

接下来到 \(2\) 号节点:发现并没有全都不属于它,也没有都属于它,因此询问它的子节点 \(2 \times 2\) 和 \(2 \times 2 +1\) 寻找(\(4\) 和 \(5\))

接下来到 \(4\) 号节点:发现全都不属于它,直接跳过。

接下来到 \(5\) 号节点:有标记,标记下传,(是叶子节点,相当于不传)发现都属于它,答案直接 \(+tree[5]\)。

接下来到 \(3\) 号节点:有标记,标记下传tree[2*3]+=(5-4+1)*3,tag[6]+=3;tree[2*3+1]+=(6-6+1),tag[7]+=3,发现并没有全都不属于它,也没有都属于它,因此询问它的子节点 \(3 \times 2\) 和 \(3 \times 2 +1\) 寻找(\(6\) 和 \(7\))

接下来到 \(6\) 号节点:有标记,标记下传tree[2*6]+=(4-4+1)*3,tag[12]+=3;tree[2*6+1]+=(5-5+1),tag[13]+=3发现都属于它,答案直接 \(+tree[6]\)。

接下来到 \(7\) 号节点:有标记,标记下传,(是叶子节点,相当于不传)发现全都不属于它,直接跳过。

这样一来时间就快很多了。

代码:

#include<bits/stdc++.h>
using namespace std;

void pushup(int cur)
{
	tree[cur]=tree[2*cur]+tree[2*cur+1];
	return ;
}

void build(int cur,int lt,int rt) //建树
{
	if(lt==rt)
	{
		tree[cur]=a[lt];
		return ;
	}
	int mid=(lt+rt)>>1;
	build(2*cur,lt,mid);
	build(2*cur+1,mid+1,rt);
	pushup(cur);
	return ;
}

void addtag(int cur,int lt,int rt,int val) //打懒标记
{
	tag[cur]+=val;
	tree[cur]+=(rt-lt+1)*val;
	return ;
}

void pushdown(int cur,int lt,int rt) //标记下传
{
	int mid=(lt+rt)>>1;
	addtag(2*cur,lt,mid,tag[cur]);
	addtag(2*cur+1,mid+1,rt,tag[cur]);
	tag[cur]=0;
	return ;
}

int query(int cur,int lt,int rt,int qx,int qy) //询问
{
	if(rt<qx||qy<lt)
		return 0;
	if(qx<=lt&&rt<=qy)
		return tree[cur];
	pushdown(cur,lt,rt);
	int mid=(lt+rt)>>1;
	return query(2*cur,lt,mid,qx,qy)+query(2*cur+1,mid+1,rt,qx,qy);
}

void update(int cur,int lt,int rt,int qx,int qy,int val) //修改
{
	if(rt<qx||qy<lt)
		return ;
	if(qx<=lt&&rt<=qy)
	{
		addtag(cur,lt,rt,val);
		return ;
	}
	pushdown(cur,lt,rt);
	int mid=(lt+rt)>>1;
	update(2*cur,lt,mid,qx,qy,val);
	update(2*cur+1,mid+1,rt,qx,qy,val);
	pushup(cur);
	return ;
}

标签:rt,cur,int,线段,tree,times,note,节点
From: https://www.cnblogs.com/xiaoluotongxue2012/p/17742399.html

相关文章

  • 线段树专题复习
    今天的主题是线段树专题复习!(什么?是昨天的?不听不听,只要我不说都不知道我鸽了一天!)好了,言归正传,我们来看一下今天的知识点们吧。Part1线段树自己不想讲了,想看的移步其他博客想看踢我,今天没时间了Part2一些优化ZKW线段树俗称重口味线段树,是一种不用递归实现的线段树,常数和......
  • note ODT
    (珂朵莉图压压惊)适用场景:不断区间修改、区间询问,数据随机ODT:olddrivertree(老司机树),又名珂朵莉树,是一个骗分的好东西。其内部是基于std::set实现的,而std::set是基于红黑树实现的,所以我觉得应该是算法,但是对于ODT究竟是算法还是数据结构有争议。在随机数据下跑得飞快,吊打......
  • 第02章 Python语法基础,IPython和Jupyter Notebooks
    第2章Python语法基础,IPython和JupyterNotebooks当我在2011年和2012年写作本书的第一版时,可用的学习Python数据分析的资源很少。这部分上是一个鸡和蛋的问题:我们现在使用的库,比如pandas、scikit-learn和statsmodels,那时相对来说并不成熟。2017年,数据科学、数据分析和机器学习的......
  • 算法:线段树
    算法:线段树哦吼!终于来学线段树啦~~拖了好久都没有敢学,主要是基础知识点不熟,代码能力太弱。但是现在已经是时候了。来看:线段树(SegmentTree)几乎是算法竞赛最常用的数据结构了,它主要用于维护区间信息(要求满足结合律)。与树状数组相比,它可以实现\(O(log⁡\n)\)的区间修改,......
  • 什么是企业级管理软件的 Release Notes
    企业级软件的ReleaseNote详解在现代商业环境中,企业级软件已经成为了组织中不可或缺的一部分。这些软件系统通常被用来管理各种业务流程,从客户关系管理到供应链管理,再到财务和人力资源管理。随着软件的不断发展和更新,确保企业级软件的正常运行变得至关重要。为了帮助用户了解每......
  • 线段树优化建图
    一个很好用的\(trick\)。我们通过例题CF786B为例。他需要我们支持点之间连边,点和区间之间连边,区间和点之间连边。支持最短路。如果我们暴力连边,显然最多是有\(n^2\)条边的。那怎么办呢,引入线段树分治。线段树分治在某些题中,我们可能会用\(v\tou\in[l,r]\)这种一个......
  • 线段裁剪:Cohen-Sutherland算法
    目录裁剪算法Cohen-Sutherland线段裁剪算法基本思想具体步骤计算分析程序代码裁剪算法计算机内部存储的图形数据量通常较大,而屏幕只显示其中一部分,因此需要确定哪些部分在显示区域内,哪些在显示区域外。这个过程称为裁剪(clipping)。裁剪是二维观察(三维观察)的重要部分,参见计算机图......
  • 2023年10月,红米(小米)note 8 pro 优化记
    看了红米的note13pro和note12turbo的参数和价格后,我决定下单买个note8pro的手机壳,确实有新手机的感觉了。我note8pro手机参数如下MIUI12.0.5内存是6G具体看下图经过优化调整后一般还剩3G内存,文件夹存了很多图标后也不再卡了优化步骤下载adbhttps://dl.google.co......
  • 权值线段树 学习笔记
    8月集训学了权值线段树,当时没怎么加强训练。国庆刚好开始有时间,巩固巩固。补上学习笔记。首先介绍权值树。其本质是一个记录每个数出现次数的线段树,也就是由桶建成的树。接下来介绍各种操作。1.插入。由于统计的是出现次数,从这个数往上依次加1即可。voidinsert(intx,int......
  • 如何管理 Jupyter Notebook 的kernel环境
    在JupyterNotebook中,你可以使用以下方法来管理kernel环境:1.安装kernel:首先,你需要安装所需的kernel。不同的编程语言和环境可能有不同的kernel。你可以使用包管理器(如pip、conda)来安装特定语言的kernel。例如,要安装Pythonkernel,你可以运行 pipinstallipykernel 命......