首页 > 其他分享 >线段树学习笔记

线段树学习笔记

时间:2025-01-15 19:44:09浏览次数:1  
标签:lf rt lc int 线段 笔记 学习 区间

什么是线段树

线段树是一种基于分治思想的二叉树结构,用于在区间上进行信息统计,比树状数组更为通用、直观,支持单点修改、区间修改、区间查询。

线段树维护的数据具有可并性,比如区间和、区间积、区间最值等等。

模板

建树

void build(int l,int r,int p)
{
	tre[p].l=l;tre[p].r=r;
	if(l==r)
    {
      //magic
      return;
    }
	int mid=(l+r)/2;
	build(l,mid,p<<1);
	build(mid+1,r,(p<<1)+1);
  psu(p);
}

修改

void upd1(int k,int x,int p)//单点修改
{
	if(tre[p].l==tre[p].r)
	{
		tre[p].v=k;return;
	}
	int mid=(tre[p].l+tre[p].r)/2;
	if(x<=mid)upd(k,x,p<<1);
	else upd(k,x,(p<<1)+1);
	tre[p].v=max(tre[p<<1].v,tre[(p<<1)+1].v);
}
void upd2(int l,int r,int k,int p)//区间修改
{
	int nl=lf[p],nr=rt[p];
	if(l<=nl&&nr<=r)
	{
		vl[p]+=1ll*k*(nr-nl+1);
		tg[p]+=k;
		return;
	}
	spr(p);
	int mid=(nl+nr)/2;
	if(l<=mid)upd(l,r,k,p<<1);
	if(r>mid)upd(l,r,k,(p<<1)+1);
	vl[p]=vl[p<<1]+vl[(p<<1)+1];
}

向下传递

void psd(int p)
{
	if(tg[p])
	{
		vl[p<<1]+=tg[p]*(rt[p<<1]-lf[p<<1]+1);
		tg[p<<1]+=tg[p];
		vl[(p<<1)+1]+=tg[p]*(rt[(p<<1)+1]-lf[(p<<1)+1]+1);
		tg[(p<<1)+1]+=tg[p];
		tg[p]=0;
	}
}

区间查询

long long query(int l,int r,int p)
{
	int nl=lf[p],nr=rt[p];
	if(l<=nl&&nr<=r)return vl[p];
	spr(p);
	int mid=(nl+nr)/2;
	long long val=0;
	if(l<=mid)val+=query(l,r,p<<1);
	if(r>mid)val+=query(l,r,(p<<1)+1);
	return val;
}

写题的时候要想清楚需要维护哪些信息,数据如何在节点之间传递等等。

Second Largest Query
单点修改,每次查询区间内次大值的出现次数。
不难看出,区间的最大值在合并前的区间的最大值中产生,次大值在次大的最大值和次大的最大值中产生(当两个最大值相等时,在两个次大值中产生)。每个节点储存区间最大值、次大值及其出现次数。

Can you answer on these queries III
单点修改,每次查询区间内的最大连续子段和。
分类讨论,最大连续子段来自于区间的左半部分或者右半部分或者左边靠右的部分和右边靠左的部分拼起来。每个节点储存区间从最左开始的最大子段和,最右开始的最大子段和以及整个区间内的最大子段和。

Vacation Query
和上面那题很像,但是区间修改需要把区间内所有的\(0 / 1\)反过来。每个节点内储存紧靠左边/右边的最大连续\(0/1\)的个数,区间内最大连续个数,向上传递时如果左子树全是\(0/1\)的话,\(lmax_{p}=len_{lson}+lmax_{rson}\),否则\(lmax_{p}=lmax_{lson}\)。更新时交换\(0/1\)对应的连续值。

【模板】线段树 2
模板plus,区间修改为整体\(\times c\)或者\(+c\),区间查询。
需要两个延迟标记,一个记录倍数,一个记录加数,传递的时候要注意先乘后加。


势能线段树

有些情况下,线段树的区间修改会比较麻烦,比如区间开根、求商、取余等等。但是不难发现,这些修改的次数是有上限的,比方说\(0/1\)开根之后还是它本身,\(0\)不管除以多少还是\(0\),那么到达这些区间时,修改的传递就可以停止了。又不难发现达到修改上限需要的次数也非常少,整体的复杂度是\(O(cn\log n)\),其中\(c\)为修改次数上限。

void upd(int l,int r,int p)
{
	if(lf(p)==rt(p))//暴力修改
	{
		mx(p)=sm(p)=sqrt(sm(p));
		return;
	}
	int mid=(lf(p)+rt(p))>>1;
	if(l<=mid&&mx(p<<1)>1)upd(l,r,p<<1);//判断边界
	if(r>mid&&mx(p<<1|1)>1)upd(l,r,p<<1|1);
	psu(p);
}

花神游历各国
就是这样。线段树每个节点维护区间和and最大值,如果最大值\(\le1\)就停止传递修改,其余递归到单点。


权值线段树

权值线段树的区间端点并非下标含义,而是值域。

逆序对
每个节点维护\([l,r]\)中数的个数。每次更新\(a_{i}\)对应节点,查询\([a_{i}+1,1e5]\)中的个数。而不要用线段树做归并排序,==。


动态开点

有时我们会遇到值域很小但是实际用到的点不多的权值线段树,很显然是没有必要把所有节点都build出来的。所以选择在访问到发现没有这个节点的时候再造一个。

struct treee{
	int lc,rc,nm;
	#define lc(p) tr[p].lc//左儿子下标
	#define rc(p) tr[p].rc//右儿子下标
	#define nm(p) tr[p].nm
}tr[N*30];
int tot=0;
void upd(int x,int p,int lf,int rt)//单点更新 插入x
{
	if(lf==rt)
	{
		nm(p)++;return;
	}
	if(x<=mid)
	{
		if(!lc(p))lc(p)=++tot;//动态开点
		upd(x,lc(p),lf,mid);
	}
	else
	{
		if(!rc(p))rc(p)=++tot;//动态开点
		upd(x,rc(p),mid+1,rt);
	}
	nm(p)=nm(lc(p))+nm(rc(p));
}

魔道研究 JZOJ 4270
动态开点一堆线段树,每棵树上二分找前 \(i\) 大,插入元素时把新增可以选择的元素加到 \(0\) 号树上,然后每次在这棵树上二分找前 \(n\) 大的元素和。


线段树二分

二分,但是在线段树上。

int query(int k,int p,int lf,int rt)//查询第k大的数
{
	if(lf==rt)return lf;
	if(k<=nm(lc(p)))return query(k,lc(p),lf,mid);
	else return query(k-nm(lc(p)),rc(p),mid+1,rt);
}

中位数
思路一对顶堆,思路二权值线段树动态开点+二分。

Siano
不难发现每次割掉的草都是长得最快的,所以排序。我的做法是两个懒标记记录上一次推平的高度和推平之后生长的天数。重点:每次推平都要把生长天数清空,向下传递推平标记的时候也要清空。


线段树合并

如果有若干棵线段树,它们都维护相同的值域 \([1,n]\) ,那么它们对各个子区间的划分显然是一致的。假设有 \(m\) 次操作,每次修改在某一棵线段树上执行。所有操作完成后我们希望把这些线段树上对应位置的值相加,这就可以通过线段树合并实现。

Promotion Counting
需要统计每棵子树中比根节点大的节点数,dfs \(+\) 权值线段树 \(+\) 合并节点。

void merge(int &a,int b,int lf,int rt)//合并
{
	if(!a)
	{
		a=b;return;
	}
	if(!b)return;
	if(lf==rt)
	{
		num[a]+=num[b];return;
	}
	merge(lc[a],lc[b],lf,mid);
	merge(rc[a],rc[b],mid+1,rt);
	num[a]=num[lc[a]]+num[rc[a]];
}

可持久化线段树(主席树)

一般的线段树维护的只是数据集的最新状态,若想知道数据集在任意时间的状态,可以在每次操作结束后创建有改动部分的副本。可持久化线段树的空间复杂度为 \(O(N+M\log N)\)( \(M\) 为修改次数)。

区间第 \(k\) 小
如果每次查询区间 \([1,N]\) 的第 \(k\) 小数,我们可以在权值线段树上进行二分。那么如果查询任意区间 \([l,r]\) 的第 \(k\) 小,看做从加入第 \(l\) 个元素到 \(r\) 个元素中间的第 \(k\) 小数。

int add(int p,int x,int lf,int rt)//加入新元素
{
	int t=++tot;
	lc[t]=lc[p];rc[t]=rc[p];num[t]=num[p]+1;//拿来不需要改动的部分
	if(lf==rt)return t;
	if(x<=mid)lc[t]=add(lc[t],x,lf,mid);
	else rc[t]=add(rc[t],x,mid+1,rt);
	return t;
}
int query(int p1,int p2,int k,int lf,int rt)//p1为时间是l-1的节点,p2为时间是r的节点
{
	if(lf==rt)return lf;
	if(num[lc[p2]]-num[lc[p1]]>=k)return query(lc[p1],lc[p2],k,lf,mid);
	else return query(rc[p1],rc[p2],k-num[lc[p2]]+num[lc[p1]],mid+1,rt);
}

扫描线

把平面上的若干个矩形看做若干 \(\times2\) 条线段,用四元组 \((x,y1,y2,k)\) 记录,处理这些重叠的矩形的问题时可以看做用一条线从左到右扫过去,问题的答案在矩形的左右边界发生变化,这些变化就可以用线段树维护。

Atlantis

给出若干个矩形,求它们的面积并。顶点坐标不一定是整数。

int main()
{
	scanf("%d",&n);
	while(n)
	{
		for(int i=1;i<=n;i++)
		{
			scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
			g[i]={a,b,d,1};g[i+n]={c,b,d,-1};//将矩形转化为线段
			y.push_back(b);y.push_back(d);
		}
		sort(y.begin(),y.end());y.erase(unique(y.begin(),y.end()),y.end());
		siz=y.size();
		build(1,1,siz);
		sort(g+1,g+n*2+1);//按照x递增的顺序排序
		int b1,c1,d1;
		for(int i=1;i<=n*2;i++)
		{
			a=g[i].x;
			b1=lower_bound(y.begin(),y.end(),g[i].y)-y.begin()+1;//离散化
			c1=lower_bound(y.begin(),y.end(),g[i].y2)-y.begin()+1;
			d1=g[i].f;
			ans+=(g[i].x-g[i-1].x)*sum[1];//修改答案
			upd(b1,c1-1,d1,1,1,siz);
		}
		printf("Test case #%d\n",++t);
		printf("Total explored area: %.2f\n\n",ans);
		reset();
		scanf("%d",&n);
	}
	return 0;
}

易错

  1. 线段树空间必须\(\times 4\)。

  2. 如果push up的时候使用了结构体,像这样:

treee psu(treee l,treee r)
{
  treee res;
  //magic
  return res;
}

建议所有变量都要初始化。

  1. #define max(a,b) ((a)>(b))?a:b

  2. 为了防止莫名其妙的TLE,建议减少使用minmax的次数。

  3. 注意空间

  4. 要注意排序运算符的定义,否则可能会RE。


只能说发明线段树的人真是个天才!

标签:lf,rt,lc,int,线段,笔记,学习,区间
From: https://www.cnblogs.com/bgf0212/p/18673638

相关文章

  • 机器学习之DBSCAN算法自动分类
    机器学习之DBSCAN算法自动分类目录机器学习之DBSCAN算法自动分类1DBSCAN算法1.1概念1.2关键概念:1.3算法步骤:1.4函数和参数1.5优缺点2实际测试2.1部分数据展示2.2代码测试1DBSCAN算法1.1概念DBSCAN(Density-BasedSpatialClusteringofApplications......
  • 机器学习之支持向量机SVM及测试
    目录1支持向量机SVM1.1概念1.2基本概念1.3主要特点1.4优缺点1.5核函数1.6常用的核函数1.7函数导入1.8函数参数2实际测试2.1二维可视化测试代码2.2多维测试1支持向量机SVM1.1概念支持向量机(SupportVectorMachine,简称SVM)是一种二分类模型,它的基本......
  • 运算放大器应用电路设计笔记(一)
    1.1何谓运算放大器1.1.1运算放大器的诞生运算放大器简称OP,于20世纪40年代作为模拟计算机功能元件开发出来。要进行加减乘除的原始运算甚至微积分运算,只需在放大器中施加特殊的负反馈就可进行运算。1.1.2作为理想元件处理电路符号用三角形表示,左端为两个输入端,右端为输出端......
  • 基于yolov4深度学习网络的排队人数统计系统matlab仿真,带GUI界面
    1.算法仿真效果matlab2022a仿真结果如下(完整代码运行后无水印):  仿真操作步骤可参考程序配套的操作视频。 2.算法涉及理论知识概要       在现代社会的众多场景中,如银行、车站、餐厅等,排队人数的统计对于资源分配、服务优化以及人员管理等方面具有极为重......
  • 七、多智能体强化学习高级主题及其趋势
    7.1高级话题7.1.1层次化强化学习(HierarchicalRL,HRL)(1)为什么需要层次化?在大规模、复杂决策场景中,直接从原始动作空间学到最优策略往往困难且收敛缓慢。层次化RL(HRL)通过在策略层面引入层级结构,让智能体分解任务为更高层的“元动作”或“子任务”,从而简化学习过......
  • Markdown学习
    标题"#"+"":一级标题"##"+"":二级标题……"#"个数加一,标题级数以1为等差后移字体样式操作示例加粗:两边加**Hello,World!斜体:两边加*Hello,World!斜体加粗:两边加***Hello,World!删除线:两边加~~HelloWorld!引用摘抄他人文章可使用引用">&qu......
  • Python Playwright学习笔记(一)
    一、简介1.1Playwright是什么?它是微软在2020年初开源的新一代自动化测试工具,其功能和selenium类似,都可以驱动浏览器进行各种自动化操作。1.2、特点是什么支持当前所有的主流浏览器,包括chrome、edge、firefox、safari;支持跨平台多语言:支持Windows、Linux、macOS;安装和......
  • MSGNet:多尺度序列间相关性学习的多变量时间序列预测
    MSGNet——多尺度序列间相关性学习的多变量时间序列预测[2401.00423v1]MSGNet:LearningMulti-ScaleInter-SeriesCorrelationsforMultivariateTimeSeriesForecasting——来自CCF-A(AAAI,AAAlConferenceonArtificialIntelligence)GitHub代码:YoZhibo/MSGNet:MS......
  • 机器学习中的凸函数和梯度下降法
    一、凸函数在机器学习中,凸函数和凸优化是优化问题中的重要概念,许多机器学习算法的目标是优化一个凸函数。这些概念的核心思想围绕着优化问题的简化和求解效率。下面从简单直观的角度来解释。1.什么是凸函数?数学定义一个函数f(x)f(x)是凸函数,当且仅当它满足以下条件:......
  • 霸道总裁重生之他要学习——《前缀和》
    前言这是笔者备考蓝桥杯自己做的学习相关内容,或有不准确,欠妥的部分,请谅解,如有问题,欢迎评论,也欢迎在评论区留言备考蓝桥杯的相关心得,寻找一同学习的学习搭子,加油同志们!一、一维前缀和1、一维前缀和的定义与性质定义:sum[i]表示数组a的前i个数的和,即为前缀和,一维前缀和习惯从0......