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

线段树合并学习笔记

时间:2025-01-05 21:45:01浏览次数:9  
标签:return lc int 线段 合并 笔记 rc 节点

前言

模拟赛 solution 里说

只需要利用线段树合并的思想……

但是我不会线段树合并,就先学习了线段树合并。

引入

线段树合并是把每个对应节点合并。

两棵线段树都有某个节点,就是把这两个点合成一个点;

只有一棵线段树有某个节点,合并出来的线段树的这个节点就是这个唯一的节点。

两棵线段树都没有这个节点,合并完之后还是没有。

可以看得出来,如果是一般的线段树(是满二叉树)合并就是暴力,所以线段树合并一般是在动态开点线段树之间进行的。

动态开点线段树

当值域很大,操作数不多时,线段树有很多浪费的空间,这时候就需要动态开点线段树。

在这棵线段树上修改节点 \(3\) 的值时,只需要用到三个节点(标红的节点)。

每一个节点记录下左儿子和右儿子,当需要某个节点却没有时,新建一个节点。

代码如下:

void ins(int &p,int l,int r,int x,int v){
	if(!p) p=++cnt;
	if(l==r){
		sum[p]+=v;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) ins(lc[p],l,mid,x,v);
	else ins(rc[p],mid+1,r,x,v);
	push_up(p);
}

查询的时候,需要注意不存在的节点不能计入答案。代码如下:

int ask(int p,int l,int r,int ql,int qr){
	if(!p) return 0;
	if(ql<=l&&r<=qr) return sum[p];
	int mid=(l+r)>>1,res=0;
	if(ql<=mid) res+=ask(lc[p],l,mid,ql,qr);
	if(qr>mid) res+=ask(rc[p],mid+1,r,ql,qr);
	return res;
}

线段树合并

主要有两种写法。

  1. 把 \(b\) 合并到 \(a\) 上。
    这种写法会丢失合并前树的信息,所以只能离线下来。

    代码还是很好理解的:

    int merge(int a,int b,int l,int r){
    	if(!a||!b) return a|b;
    	if(l==r){
    		sum[a]+=sum[b];
    		return a;
    	}
    	int mid=(l+r)>>1;
    	lc[a]=merge(lc[a],lc[b],l,mid);
    	rc[a]=merge(rc[a],rc[b],mid+1,r);
    	push_up(a);
    	return a;
    }
    
  2. 新建一个节点,把 \(a\) 和 \(b\) 合并信息存下来。

    代码更好理解:

    int merge(int a,int b,int l,int r){
        if(!a||!b) return a|b;
        int c=++cnt;
        if(l==r){
    		sum[c]=sum[a]+sum[b];
    		return;
    	}
        int mid=(l+r)>>1;
    	lc[c]=merge(lc[a],lc[b],l,mid);
    	rc[c]=merge(rc[a],rc[b],mid+1,r);
    	push_up(c);
    	return c;
    }
    

例题

P4556 [Vani有约会] 雨天的尾巴 /【模板】线段树合并

每个节点开一个动态开点值域线段树。

离线,把修改操作在树上差分,最后一遍 dfs 从下向上用线段树合并,到一个点,把它的所有儿子的信息进行合并。

代码:

#include<bits/stdc++.h>
#define endl '\n'
#define debug(x) cerr<<#x<<':'<<x<<endl
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
const int N=1e5+5,V=1e5;
int n,m;

int root[N];
int mx[N*80],pos[N*80];
int lc[N*80],rc[N*80];
int cnt;
void push_up(int p){
	if(mx[lc[p]]>=mx[rc[p]]) mx[p]=mx[lc[p]],pos[p]=pos[lc[p]];
	else mx[p]=mx[rc[p]],pos[p]=pos[rc[p]];
}
void ins(int &p,int l,int r,int x,int v){
	if(!p) p=++cnt;
	if(l==r){
		mx[p]+=v,pos[p]=l;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) ins(lc[p],l,mid,x,v);
	else ins(rc[p],mid+1,r,x,v);
	push_up(p);
}
int merge(int a,int b,int l,int r){
	if(!a||!b) return a|b;
	if(l==r){
		mx[a]+=mx[b],pos[a]=l;
		return a;
	}
	int mid=(l+r)>>1;
	lc[a]=merge(lc[a],lc[b],l,mid);
	rc[a]=merge(rc[a],rc[b],mid+1,r);
	push_up(a);
	return a;
}
vector<int> G[N];
int fa[N],dep[N],siz[N],son[N];
int top[N],dfn[N],idx;
void dfs1(int x,int faa){
	fa[x]=faa,dep[x]=dep[faa]+1,siz[x]=1;
	for(int y:G[x]){
		if(y==faa) continue;
		dfs1(y,x);
		siz[x]+=siz[y];
		if(siz[y]>siz[son[x]]) son[x]=y;
	}
}
void dfs2(int x,int hd){
	dfn[x]=++idx,top[x]=hd;
	if(son[x]) dfs2(son[x],hd);
	for(int y:G[x]){
		if(y==fa[x]||y==son[x]) continue;
		dfs2(y,y);
	}
}
int lca(int x,int y){
	while(top[x]^top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		x=fa[top[x]];
	}
	if(dfn[x]>dfn[y]) swap(x,y);
	return x;
}
int ans[N];
void dfs(int x){
	for(int y:G[x]){
		if(y==fa[x]) continue;
		dfs(y);
		root[x]=merge(root[x],root[y],1,V);
	}
	if(mx[root[x]]) ans[x]=pos[root[x]];
}
signed main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	IOS;
	cin>>n>>m;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs1(1,0),dfs2(1,1);
	for(int i=1,x,y,z;i<=m;i++){
		cin>>x>>y>>z;
		int lc=lca(x,y);
		ins(root[x],1,V,z,1);
		ins(root[y],1,V,z,1);
		ins(root[lc],1,V,z,-1);
		if(fa[lc]) ins(root[fa[lc]],1,V,z,-1);
	}
	dfs(1);
	for(int i=1;i<=n;i++) cout<<ans[i]<<endl;
	return 0;
}

标签:return,lc,int,线段,合并,笔记,rc,节点
From: https://www.cnblogs.com/zhangjiting/p/18653953

相关文章

  • AAAT 笔记(P56491)
    实际上去掉主函数不长于线段树3。原理还没写#include<bits/stdc++.h>usingnamespacestd;#defineintlonglong#defineendl"\n"constintmaxn=4e5+5,INF=1e12;structtag{ intk,b; tag(intx=1,inty=0){k=x,b=y;}}rtag[maxn],vtag[maxn];structnode{ intmn......
  • LFS 笔记
    简介一直想知道一个发行版的代码是怎么构成的,这个项目可以带我们自己构建一个Linux发行版,并且可以运行.您可能有许多阅读本书的理由。许多人首先会问:“为什么要不辞辛苦地手工从头构建一个Linux系统,而不是直接下载并且安装一个现成的?”LFS项目存在的一项重要原因是,它能......
  • 笔记 HarmonyOS:ArkTS-回顾
    1.声明式UI开发:2.组件语法容器组件(参数){内容}.属性1().属性2().属性...()普通组件(参数).属性1().属性2().属性...() 3.typeof运算符functionfunc(){numb:Number}classPerson{name:string='Tom'}@Entry@ComponentstructTypeofPage{......
  • 线段树优化建图
    更新日志2025/01/05:开工。概念利用线段树优化建图。一般情况下,会出现点和区间或区间和区间连边的情况,就可以考虑线段树优化建图了。思路开两棵线段树,一棵储存入边,一棵储存出边。每个节点都代表对应的区间。入树中每个点都指向其子节点,出树中相反。区间连边时,在对应线......
  • STM32-笔记36-ADC(模拟/数字转换器)
    一、什么是ADC?        全称:Analog-to-DigitalConverter,指模拟/数字转换器。        ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。12位ADC是一种逐次逼近型模拟数字转换器(0~4095(2^12))。它有多达18个......
  • [数据结构学习笔记4] 堆栈(Stack)
    堆栈,我们总是把新的数据加在堆栈的最顶端,移除的时候也是从最顶端开始移除。也叫LIFO(lastinfirstout)。代码实现(javascript)classStack{constructor(...items){this.items=items;}clear(){this.items.length=0;}clon......
  • Redis数据库笔记——ZSet的底层实现(跳表)
    大家好,这里是GoodNote,关注公主号:Goodnote,专栏文章私信限时Free。本文详细介绍ZSet数据类型中跳表的底层实现,包括基本特点和常用操作。文章目录ZSet(有序集合)概述基本特点底层实现Skiplist跳表概述结构跳表的基本操作1.查找操作:`Search`2.插入操作:`Insert`3.删......
  • 多模态论文笔记——U-ViT(国内版DiT)
    大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本文详细介绍U-ViT的模型架构和实验细节,虽然没有后续的DiT在AIGC领域火爆,但为后来的研究奠定了基础,但其开创性的探索值得学习。文章目录论文背景架构训练细节1.长跳跃连接(LongSkipConnections)2.时......
  • Redis数据库笔记—— Hash(哈希)的扩容机制(rehash)
    大家好,这里是GoodNote,关注公主号:Goodnote,专栏文章私信限时Free。详细介绍Hash(哈希)的扩容机制(rehash)、源码、以及扩容和缩容过程。文章目录Redis字典(dict)结构源码哈希表结构定义渐进式哈希扩容(rehash)渐进式哈希的优点扩容机制:rehash扩容条件扩容过程缩容机制:r......
  • 【AI学习笔记5】用C语言实现一个最简单的MLP网络 A simple MLP Neural network in C
    用C语言实现一个最简单的MLP网络AsimpleMLPNeural NetworkinClanguage 从图像中识别英文字母【1】从图像中识别多个不同的数字,属于多分类问题;每个图像是5*5的像素矩阵,分别包含1-5五个字母数字; 网络结构:一个隐藏层的MLP网络;       每个图像是5x5个......